EonaCat.SecureToken 0.0.4

Prefix Reserved
dotnet add package EonaCat.SecureToken --version 0.0.4
                    
NuGet\Install-Package EonaCat.SecureToken -Version 0.0.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.SecureToken" Version="0.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EonaCat.SecureToken" Version="0.0.4" />
                    
Directory.Packages.props
<PackageReference Include="EonaCat.SecureToken" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EonaCat.SecureToken --version 0.0.4
                    
#r "nuget: EonaCat.SecureToken, 0.0.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EonaCat.SecureToken@0.0.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EonaCat.SecureToken&version=0.0.4
                    
Install as a Cake Addin
#tool nuget:?package=EonaCat.SecureToken&version=0.0.4
                    
Install as a Cake Tool

EonaCat.SecureToken

Secure, modern token library for .NET with key rotation, signing isolation, validation rules, and .NET Standard support.

EonaCat.SecureToken provides a safer alternative to rolling your own authentication tokens. It focuses on:

  • Strong cryptographic signing
  • Versioned key management
  • Token lifecycle validation
  • Refresh/access token separation
  • Extensible claims
  • API-friendly validation results
  • .NET Standard 2.0 compatibility

Features

Cryptographic protection

  • HMAC based token signing
  • HKDF derived context-specific keys
  • Constant-time signature verification (including binding-context comparison)
  • Tamper detection
  • Strong random key generation
  • Bounded decoding: oversized or field-flooded tokens are rejected before signature verification, so malformed input cannot be used to exhaust memory

Validation

  • Issuer, audience (single or multi-audience accept-list), and token-type checks
  • Expiry, not-before, and an independent MaxTokenAge backstop measured from issuance
  • Token binding (IP, device fingerprint, TLS channel hash, etc.)
  • Pluggable revocation check
  • Pluggable replay-cache for one-time-use tokens (password reset, email verification, invitations)
  • OnValidated audit hook fired for every validation attempt

Key rotation

Rotate signing keys without invalidating existing tokens.

Example:

var store = SigningKeyStore.CreateNew();

var service = new TokenService(store);

var oldToken = service.Issue(
    TokenDescriptor.Create()
        .ForSubject("user-123")
        .IssuedBy("my-api")
        .ForAudience("mobile")
);

// Rotate keys
store.Rotate();

// New tokens use the new key
var newToken = service.Issue(
    TokenDescriptor.Create()
        .ForSubject("user-456")
        .IssuedBy("my-api")
        .ForAudience("mobile")
);

// Old token still validates
service.Validate(
    oldToken,
    TokenValidationOptions.AccessToken("my-api", "mobile")
);

Installation

Install from NuGet:

dotnet add package EonaCat.SecureToken

Quick Start

Create a token service

using EonaCat.SecureToken.Core;
using EonaCat.SecureToken.Cryptography;

var keys = SigningKeyStore.CreateNew();

var tokens = new TokenService(keys);

Issue an access token

var token = tokens.Issue(
    TokenDescriptor.Create()
        .ForSubject("user-123")
        .IssuedBy("my-service")
        .ForAudience("api")
        .WithRole("admin")
        .WithClaim("email", "user@example.com")
);

The token contains:

  • Subject
  • Issuer
  • Audience
  • Roles
  • Custom claims
  • Token ID
  • Expiration
  • Key generation information

Validate a token

var result = tokens.Validate(
    token,
    TokenValidationOptions.AccessToken(
        issuer: "my-service",
        audience: "api"
    )
);

if (result.IsSuccess)
{
    var claims = result.UnwrapClaims();

    Console.WriteLine(claims.Subject);
}

Token expiration

var token = tokens.Issue(
    TokenDescriptor.Create()
        .ForSubject("user-1")
        .IssuedBy("api")
        .ForAudience("mobile")
        .WithLifetime(TimeSpan.FromMinutes(15))
);

Expired tokens are automatically rejected.

Refresh tokens

Create a refresh token:

var pair = tokens.IssueTokenPair(
    "user-1",
    "api",
    "mobile"
);

Console.WriteLine(pair.AccessToken);
Console.WriteLine(pair.RefreshToken);

Validate separately:

tokens.Validate(
    pair.RefreshToken,
    TokenValidationOptions.RefreshToken("api")
);

Refresh tokens cannot be used as access tokens.

Token binding

Bind tokens to a context such as a device or session:

var token = tokens.Issue(
    TokenDescriptor.Create()
        .ForSubject("user-1")
        .IssuedBy("api")
        .ForAudience("web")
        .BoundTo("device-identifier")
);

Validation:

new TokenValidationOptions
{
    ValidIssuer = "api",
    ValidAudience = "web",
    BindingContext = "device-identifier"
};

Revocation

You can integrate your own revocation storage:

var options = new TokenValidationOptions
{
    ValidIssuer = "api",
    ValidAudience = "web",

    RevocationCheck = async (tokenId, cancellationToken) =>
    {
        return await database.IsRevoked(tokenId);
    }
};

Replay protection for one-time-use tokens

Revocation answers "has someone explicitly blocked this token?" Replay protection answers a different question: "has this exact token already been used once?" Use it for password-reset links, email-verification links, and invitations - anything that should only ever be redeemed a single time, even before it expires.

var replayCache = new InMemoryReplayCache(); // register as a singleton in DI

var options = TokenValidationOptions.OneTimeUse(
    issuer: "api",
    tokenType: TokenTypeConstants.PasswordReset,
    maxAge: TimeSpan.FromMinutes(15));

options.ReplayCache = replayCache;

var result = await tokens.ValidateAsync(token, options);
// Second call with the same token returns TokenResult.Replayed instead of Success.

InMemoryReplayCache is process-local and fine for a single instance. For multi-instance deployments, implement IReplayCache against a shared store (Redis, or a database table with a unique constraint on the token ID) so replay detection works across all instances.

Replay consumption only happens through ValidateAsync, since it has a side effect (recording the token as used) - the synchronous Validate never touches the replay cache.

Multiple audiences

Use ValidAudiences when the same access token needs to be accepted by more than one downstream service:

var options = TokenValidationOptions.AccessToken(
    issuer: "api",
    audiences: new[] { "service-a", "service-b", "service-c" });

The token is accepted if any one of its own audiences matches any one of the configured set.

Audit and introspection hook

OnValidated is invoked for every validation attempt, success or failure, and is intended for audit logging or metrics - not for authorization decisions:

var options = TokenValidationOptions.AccessToken("api", "web");

options.OnValidated = e =>
{
    logger.LogInformation("Token validation: {Result} sub={Subject}", e.Result, e.Subject);
};

Subject and TokenId on the event are only populated when the result is a success, since claims are never trusted before the signature has verified.

ASP.NET Core dependency injection

builder.Services.AddSecureTokens();

or provide your own key store:

builder.Services.AddSecureTokens(
    store =>
    {
        return SigningKeyStore.FromKeys(
            new[]
            {
                (1, secretKeyBytes)
            });
    });

To enable replay protection via DI:

builder.Services.AddSecureTokenReplayProtection();

Security design

The library separates cryptographic purposes:

Master Key
    |
    +-- Signing Key
    |
    +-- Encryption Key
    |
    +-- Context-specific keys

This prevents accidental key reuse between operations.

Supported frameworks

  • .NET Standard 2.0
  • .NET Standard 2.1
  • .NET Framework 4.8
  • .NET 8+

When to use

Good fit for:

APIs
Microservices
Internal authentication
Service-to-service tokens
Applications needing key rotation

When not to use

Do not store secrets directly in source code.

Use:

  • Environment variables
  • Secret managers
  • Hardware-backed key storage where required

License

Apache License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
0.0.4 140 6/22/2026
0.0.3 134 6/20/2026
0.0.2 114 6/19/2026