JWT Configuration
Titan uses JSON Web Tokens (JWT) for authenticating API requests.
Configuration
Configure JWT in appsettings.json:
{
"Jwt": {
"Key": "your-secret-key-minimum-32-characters-long",
"Issuer": "Titan",
"Audience": "TitanClient",
"AccessTokenExpirationMinutes": 15,
"RefreshTokenExpirationDays": 7
}
}
| Setting | Description | Default |
|---|---|---|
Key | HMAC-SHA256 signing key (≥32 chars) | Required |
Issuer | Token issuer claim | Titan |
Audience | Token audience claim | TitanClient |
AccessTokenExpirationMinutes | Access token lifetime | 15 |
RefreshTokenExpirationDays | Refresh token lifetime | 7 |
[!WARNING] The
Keymust be at least 32 characters. Keep it secret and rotate periodically.
Token Claims
Access tokens include these claims:
| Claim | Description | Example |
|---|---|---|
sub | User ID (GUID) | 550e8400-e29b-41d4-a716-446655440000 |
iss | Issuer | Titan |
aud | Audience | TitanClient |
exp | Expiration timestamp | Unix timestamp |
provider | Auth provider | EOS, Mock |
role | User role(s) | Player, Admin |
Token Generation
Tokens are generated by TokenService:
public class TokenService : ITokenService
{
public string GenerateAccessToken(Guid userId, string provider, IEnumerable<string> roles)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, userId.ToString()),
new("provider", provider)
};
foreach (var role in roles)
claims.Add(new(ClaimTypes.Role, role));
var token = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
expires: DateTime.UtcNow.Add(AccessTokenExpiration),
signingCredentials: _credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
SignalR Authentication
SignalR hubs receive the token via query parameter:
wss://api.example.com/accountHub?access_token=eyJhbG...
The API extracts and validates this token:
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
var hubPaths = new[] { "/accountHub", "/authHub", "/characterHub", ... };
if (!string.IsNullOrEmpty(accessToken) &&
hubPaths.Any(p => path.StartsWithSegments(p)))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
Authorization Policies
Titan defines these authorization policies:
SuperAdmin Policy
Requires Admin or SuperAdmin role:
options.AddPolicy("SuperAdmin", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
c.Type == ClaimTypes.Role &&
(c.Value.Equals("Admin", StringComparison.OrdinalIgnoreCase) ||
c.Value.Equals("SuperAdmin", StringComparison.OrdinalIgnoreCase)))));
AdminDashboard Policy
Requires any admin role (SuperAdmin, Admin, or Viewer):
options.AddPolicy("AdminDashboard", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
c.Type == ClaimTypes.Role &&
(c.Value.Equals("SuperAdmin", StringComparison.OrdinalIgnoreCase) ||
c.Value.Equals("Admin", StringComparison.OrdinalIgnoreCase) ||
c.Value.Equals("Viewer", StringComparison.OrdinalIgnoreCase)))));
Refresh Tokens
Refresh tokens are stored in the RefreshTokenGrain:
Key features:
- Token rotation: Consumed tokens are invalidated
- Multi-device support: Multiple active refresh tokens per user
- Security revocation: Revoke all tokens on password change/breach
Best Practices
- Store access tokens in memory only - They're short-lived
- Store refresh tokens securely - Use secure storage (Keychain, Credential Manager)
- Handle expiration gracefully - Refresh before expiration
- Implement retry with refresh - If 401, refresh and retry once