Bewit.Extensions.HotChocolate
8.1.0
dotnet add package Bewit.Extensions.HotChocolate --version 8.1.0
NuGet\Install-Package Bewit.Extensions.HotChocolate -Version 8.1.0
<PackageReference Include="Bewit.Extensions.HotChocolate" Version="8.1.0" />
<PackageVersion Include="Bewit.Extensions.HotChocolate" Version="8.1.0" />
<PackageReference Include="Bewit.Extensions.HotChocolate" />
paket add Bewit.Extensions.HotChocolate --version 8.1.0
#r "nuget: Bewit.Extensions.HotChocolate, 8.1.0"
#:package Bewit.Extensions.HotChocolate@8.1.0
#addin nuget:?package=Bewit.Extensions.HotChocolate&version=8.1.0
#tool nuget:?package=Bewit.Extensions.HotChocolate&version=8.1.0
Bewit
Bewit is an authentication scheme for secure, temporary access tokens.
Bewit enables authentication in use cases where cookies and auth headers can't be used — file downloads, temporary links, single-use tokens, and share links with admin-controlled expiry.
Features
- Self-Contained tokens — expiry embedded in the token (stateless)
- Server-Controlled tokens — expiry managed in the database; requires
UseMongoDb()orUseNonceRepository() - Token revocation — type-safe
IBewitTokenRevoker<T>to revoke tokens by identifier - Multi-tenancy — different secrets and modes per payload type
- MongoDB persistence — with OIDC auth support (Azure.Identity)
- HotChocolate integration —
[Bewit<T>]attribute for GraphQL resolvers - MVC integration —
[BewitMvc],[FromBewit],[BewitUrlAuthorization]filters - Aspire-ready — connection string pattern for distributed apps
Quick Start
Install
dotnet add package Bewit
dotnet add package Bewit.Generation
dotnet add package Bewit.Validation
Registration
From appsettings.json:
{
"Bewit": {
"Secret": "your-secret-at-least-32-chars!",
"TokenDuration": "00:05:00",
"ExpiryMode": "SelfContained"
}
}
services.AddBewit(bewit =>
{
bewit.BindConfiguration("Bewit");
bewit.AddPayload<string>();
});
services.AddBewitGeneration<string>();
services.AddBewitValidation<string>();
Or code-only:
services.AddBewit(bewit =>
{
bewit.ConfigureOptions(o =>
{
o.Secret = "your-secret-at-least-32-chars!";
o.TokenDuration = TimeSpan.FromMinutes(5);
o.ExpiryMode = ExpiryMode.SelfContained;
});
bewit.AddPayload<string>();
});
services.AddBewitGeneration<string>();
services.AddBewitValidation<string>();
Both can be combined — BindConfiguration loads from appsettings first, then ConfigureOptions overrides specific values.
Configuration
v7.0 supports appsettings.json binding, code-based configuration, or both. When combined, code wins (standard .NET options layering: Bind → Configure → PostConfigure).
From appsettings.json
{
"Bewit": {
"Secret": "your-secret-at-least-32-chars!",
"TokenDuration": "00:05:00",
"ExpiryMode": "SelfContained"
}
}
services.AddBewit(bewit =>
{
bewit.BindConfiguration("Bewit");
bewit.AddPayload<string>();
});
Code overrides on top of config
Aspire-friendly — IConfiguration is read at resolve time, not registration time:
services.AddBewit(bewit =>
{
bewit.BindConfiguration("Bewit");
bewit.ConfigureOptions(o =>
{
o.TokenDuration = TimeSpan.FromMinutes(30); // overrides appsettings value
});
bewit.AddPayload<string>();
});
Per-payload config section
Each payload type can bind to its own config section:
{
"Bewit": {
"Secret": "global-secret",
"TokenDuration": "00:05:00"
},
"Bewit:Downloads": {
"Secret": "download-secret",
"TokenDuration": "00:01:00",
"ExpiryMode": "ServerControlled"
}
}
services.AddBewit(bewit =>
{
bewit.BindConfiguration("Bewit");
bewit.AddPayload<string>();
bewit.AddPayload<DownloadPayload>(p =>
p.BindConfiguration("Bewit:Downloads")); // overrides global section
});
Code-only (no appsettings)
services.AddBewit(bewit =>
{
bewit.ConfigureOptions(o =>
{
o.Secret = "your-secret";
o.TokenDuration = TimeSpan.FromMinutes(5);
});
bewit.AddPayload<string>();
});
Generate a Token
var generator = serviceProvider.GetRequiredService<IBewitTokenGenerator<string>>();
BewitToken<string> token = await generator.GenerateBewitTokenAsync(
"my-payload", null, cancellationToken);
Validate a Token
var validator = serviceProvider.GetRequiredService<IBewitTokenValidator<string>>();
string payload = await validator.ValidateBewitTokenAsync(token, cancellationToken);
Server-Controlled Tokens
ExpiryMode.ServerControlled requires a persistent nonce repository.
The app fails at startup if ServerControlled is set without UseMongoDb() / UseNonceRepository().
services.AddBewit(bewit =>
{
bewit.ConfigureOptions(o =>
{
o.Secret = "your-secret";
o.TokenDuration = TimeSpan.FromDays(7);
o.ExpiryMode = ExpiryMode.ServerControlled;
});
bewit.UseMongoDb(mongo =>
{
mongo.ConnectionString = "mongodb://localhost:27017";
mongo.DatabaseName = "myapp";
});
bewit.AddPayload<ShareLinkPayload>();
});
Token Revocation
Inject IBewitTokenRevoker<T> to revoke all tokens for an identifier — no keyed DI magic strings needed:
public class Mutation(IBewitTokenRevoker<BarPayload> revoker)
{
public async Task<string> InvalidateTokens(string identifier, CancellationToken ct)
{
await revoker.RevokeByIdentifierAsync(identifier, ct);
return identifier;
}
}
MongoDB with OIDC (Azure Cosmos DB)
bewit.UseMongoDb(mongo =>
{
mongo.ConnectionString = "mongodb+srv://...";
mongo.DatabaseName = "mydb";
mongo.AuthType = MongoAuthType.Oidc;
mongo.OidcScopes = ["https://cosmos-db-scope/.default"];
});
Or reuse an existing IMongoDatabase from DI — the recommended approach when the service already uses MongoDB.Extensions.Context:
// Register the context once
services.AddMongoDbContext<MyDbContext, IMyDbContext>(
configurationSection: "MongoDb");
// Bewit reuses its IMongoDatabase — no second connection opened
services.AddBewit(bewit =>
{
bewit.UseMongoDb(
sp => sp.GetRequiredService<IMyDbContext>().Database,
mongo => { mongo.NonceUsage = NonceUsage.OneTime; });
bewit.AddPayload<MyPayload>(p =>
p.ConfigureOptions(o => o.ExpiryMode = ExpiryMode.ServerControlled));
});
This shares the connection pool and inherits the convention packs, serializer registrations, and read/write concerns configured on the context.
Multiple Payloads with Shared MongoDB
All payloads inherit the builder-level MongoDB and options. Per-payload overrides are possible:
services.AddBewit(bewit =>
{
bewit.ConfigureOptions(o =>
{
o.Secret = "your-secret";
o.ExpiryMode = ExpiryMode.ServerControlled;
});
bewit.UseMongoDb(mongo =>
{
mongo.ConnectionString = "mongodb://localhost:27017";
mongo.DatabaseName = "myapp";
mongo.NonceUsage = NonceUsage.ReUse;
});
bewit.AddPayload<NominationBewitContext>();
bewit.AddPayload<UserRegistrationBewitContext>();
bewit.AddPayload<DocumentDownloadBewitContext>();
// Override: self-contained, no MongoDB needed
bewit.AddPayload<DownloadBewitContext>(p =>
{
p.ConfigureOptions(o =>
{
o.ExpiryMode = ExpiryMode.SelfContained;
o.TokenDuration = TimeSpan.FromMinutes(5);
});
});
});
HotChocolate Integration
dotnet add package Bewit.Extensions.HotChocolate
[Bewit<T>] Attribute
[Mutation]
[Bewit<NominationBewitContext>(ExceptionType = typeof(BewitValidationException))]
public static async Task<NominationDto> AssignNomineeAsync(
[Service] IHttpContextAccessor httpContextAccessor, ...)
{
var context = httpContextAccessor.GetBewitPayload<NominationBewitContext>();
}
Setup
app.UseBewitTokenExtraction();
HTTP Endpoint Integration
dotnet add package Bewit.Http
Minimal API — Endpoint Filter (recommended)
Apply authorization to individual routes or route groups:
app.MapGet("/files/{id}", (string id) => ...)
.AddBewitAuthorization<MyPayload>();
// or protect a group of endpoints:
app.MapGroup("/api/files")
.AddBewitAuthorization<MyPayload>();
The filter validates the token from the configured header, query parameter, or pre-extracted HttpContext.Items entry, and makes the payload available via GetBewitPayload<T>().
Middleware (global)
Protect all endpoints via middleware:
app.UseBewitEndpointAuthorization<MyPayload>();
Token Extraction
All extensions (HotChocolate, Http, Mvc) read the bewit token from the same configurable sources: an HTTP header and/or a query parameter. Header takes precedence when both are present.
Defaults match the v6.x behavior:
- Header:
bewitToken - Query parameter:
bewit
Code configuration
services.AddBewit(bewit =>
{
bewit.ConfigureTokenExtraction(o =>
{
o.HeaderName = "X-Custom-Token";
o.QueryParamName = "token";
});
bewit.AddPayload<string>();
});
appsettings.json
{
"Bewit:TokenExtraction": {
"HeaderName": "X-Custom-Token",
"QueryParamName": "token"
}
}
services.AddBewit(bewit =>
{
bewit.BindTokenExtractionConfiguration("Bewit:TokenExtraction");
bewit.AddPayload<string>();
});
Both can be combined — BindTokenExtractionConfiguration loads from appsettings first, then ConfigureTokenExtraction overrides specific values (standard .NET options layering).
MVC Integration
dotnet add package Bewit.Extensions.Mvc
[BewitUrlAuthorization]
[HttpGet("download/{id}")]
public IActionResult Download(string id) { ... }
Packages
| Package | Description |
|---|---|
Bewit |
Core abstractions, models, crypto, DI |
Bewit.Generation |
Token generation |
Bewit.Validation |
Token validation |
Bewit.Storage.MongoDB |
MongoDB nonce repository with OIDC support |
Bewit.Extensions.HotChocolate |
HotChocolate [Bewit<T>], middleware |
Bewit.Extensions.Mvc |
MVC filters and parameter binding |
Bewit.Http |
Minimal API endpoint authorization |
Migration
See Migration Guide v7.
Community
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the Swiss Life OSS Code of Conduct.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Bewit (>= 8.1.0)
- Bewit.Generation (>= 8.1.0)
- Bewit.Validation (>= 8.1.0)
- HotChocolate.Execution.Abstractions (>= 16.0.0)
- HotChocolate.Types (>= 16.0.0)
-
net8.0
- Bewit (>= 8.1.0)
- Bewit.Generation (>= 8.1.0)
- Bewit.Validation (>= 8.1.0)
- HotChocolate.Execution.Abstractions (>= 16.0.0)
- HotChocolate.Types (>= 16.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 8.1.0 | 435 | 5/23/2026 |
| 8.0.0 | 161 | 5/12/2026 |
| 7.0.0 | 109 | 5/8/2026 |
| 7.0.0-preview.5 | 52 | 5/6/2026 |
| 7.0.0-preview.3 | 57 | 5/4/2026 |
| 7.0.0-preview.2 | 63 | 5/4/2026 |
| 7.0.0-preview.1 | 60 | 5/4/2026 |
| 6.1.0-preview.1 | 66 | 4/28/2026 |
| 6.0.0 | 1,240 | 12/3/2025 |
| 6.0.0-preview.3 | 654 | 12/3/2025 |
| 6.0.0-preview.2 | 232 | 4/23/2025 |
| 6.0.0-preview.1 | 2,784 | 4/23/2025 |
| 5.0.0 | 2,506 | 4/23/2025 |
| 5.0.0-rc.2 | 1,477 | 11/14/2024 |
| 5.0.0-rc.1 | 153 | 10/17/2024 |
| 5.0.0-preview.9 | 225 | 4/23/2025 |
| 5.0.0-preview.8 | 222 | 4/23/2025 |
| 5.0.0-preview.7 | 163 | 10/9/2024 |
| 5.0.0-preview.6 | 166 | 10/9/2024 |
| 5.0.0-preview.5 | 138 | 10/8/2024 |
Release notes: https://github.com/SwissLife-OSS/bewit/releases/8.1.0