Skip to main content

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
}
}
SettingDescriptionDefault
KeyHMAC-SHA256 signing key (≥32 chars)Required
IssuerToken issuer claimTitan
AudienceToken audience claimTitanClient
AccessTokenExpirationMinutesAccess token lifetime15
RefreshTokenExpirationDaysRefresh token lifetime7

[!WARNING] The Key must be at least 32 characters. Keep it secret and rotate periodically.

Token Claims

Access tokens include these claims:

ClaimDescriptionExample
subUser ID (GUID)550e8400-e29b-41d4-a716-446655440000
issIssuerTitan
audAudienceTitanClient
expExpiration timestampUnix timestamp
providerAuth providerEOS, Mock
roleUser 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

  1. Store access tokens in memory only - They're short-lived
  2. Store refresh tokens securely - Use secure storage (Keychain, Credential Manager)
  3. Handle expiration gracefully - Refresh before expiration
  4. Implement retry with refresh - If 401, refresh and retry once