Skip to main content
Published
May 4, 2026
5mo ago
Total Downloads
40.6K
40,618

Install

dotnet add package Bewit.Extensions.Mvc --version 7.0.0-preview.1
ProductVersions
.NET
net10.0

README

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() or UseNonceRepository()
  • Sliding window β€” token expiry extends on each successful validation (server-controlled only)
  • 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 with Sliding Window

Both ExpiryMode.ServerControlled and SlidingWindow require a persistent nonce repository. The app fails at startup if ServerControlled is set without UseMongoDb() / UseNonceRepository(), or if SlidingWindow is set without ServerControlled.

services.AddBewit(bewit =>
{
    bewit.ConfigureOptions(o =>
    {
        o.Secret = "your-secret";
        o.TokenDuration = TimeSpan.FromDays(7);
        o.ExpiryMode = ExpiryMode.ServerControlled;
        o.SlidingWindow = TimeSpan.FromHours(1);
    });

    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.UseBewitTokenHeaderExtraction();

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 from v1.x

See Migration Guide.

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.

Changes from Previous Version

v6.1.0-preview.1v7.0.0-preview.1
Added Frameworks
net10.0
Removed Frameworks
net8.0
Added Dependencies
Bewit(net10.0)
Removed Dependencies
Bewit(net8.0)

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

This version is marked prerelease. Validate its API and upgrade path before production use.No SPDX license expression is published. Review the license text and your organisation's policy before redistribution.Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.