Updated
This commit is contained in:
@@ -18,9 +18,20 @@ EonaCat.SecureToken provides a safer alternative to rolling your own authenticat
|
||||
|
||||
- HMAC based token signing
|
||||
- HKDF derived context-specific keys
|
||||
- Constant-time signature verification
|
||||
- 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
|
||||
|
||||
@@ -205,6 +216,64 @@ var options = new TokenValidationOptions
|
||||
};
|
||||
```
|
||||
|
||||
# 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.
|
||||
|
||||
```csharp
|
||||
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:
|
||||
|
||||
```csharp
|
||||
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:
|
||||
|
||||
```csharp
|
||||
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
|
||||
|
||||
```csharp
|
||||
@@ -225,6 +294,13 @@ builder.Services.AddSecureTokens(
|
||||
});
|
||||
```
|
||||
|
||||
To enable replay protection via DI:
|
||||
|
||||
```csharp
|
||||
builder.Services.AddSecureTokenReplayProtection();
|
||||
```
|
||||
|
||||
|
||||
# Security design
|
||||
|
||||
The library separates cryptographic purposes:
|
||||
|
||||
Reference in New Issue
Block a user