API Gateway Authentication Patterns: JWT, OAuth2, and API Keys Complete Guide 2025
API gateway authentication is critical for securing microservices architectures. By centralizing authentication at the gateway, you can protect backend services, simplify client implementations, and enforce consistent security policies. Different authentication patterns serve different use cases, from user-facing applications to service-to-service communication.
This comprehensive guide covers authentication patterns for API gateways, including JWT validation, OAuth2 flows, API key management, and service-to-service authentication. You'll learn how to implement these patterns in Ocelot, YARP, and Kong, handle token validation, manage refresh tokens, and deploy production-ready authentication configurations.
Authentication Patterns Overview
Why Authenticate at the Gateway?
Centralizing authentication at the API gateway provides:
- Single Point of Control: Consistent security policies
- Backend Protection: Services don't need auth logic
- Simplified Clients: Standard authentication flow
- Performance: Token validation at edge
- Observability: Centralized auth logging
Authentication Methods
1. JWT (JSON Web Tokens)
- Stateless token validation
- Self-contained claims
- Widely supported
- Good for microservices
2. OAuth2
- Industry-standard authorization
- Supports multiple grant types
- User consent flows
- Token refresh support
3. API Keys
- Simple authentication
- Service-to-service
- Easy to implement
- Less secure than tokens
4. mTLS (Mutual TLS)
- Certificate-based
- Strong security
- Service-to-service
- Complex setup
JWT Authentication
JWT Structure
JWTs consist of three parts:
- Header: Algorithm and token type
- Payload: Claims (user info, permissions)
- Signature: Verification signature
JWT Validation in Ocelot
Configure JWT authentication in Ocelot:
{
"Routes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"UpstreamPathTemplate": "/api/{everything}",
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": ["api.read", "api.write"]
}
}
],
"GlobalConfiguration": {
"AuthenticationProviderKey": "Bearer"
}
}
builder.Services.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://auth.example.com";
options.Audience = "api.example.com";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddOcelot();
JWT Validation in YARP
Implement JWT validation middleware:
builder.Services.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://auth.example.com";
options.Audience = "api.example.com";
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapReverseProxy(proxyPipeline =>
{
proxyPipeline.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized");
return;
}
await next();
});
});
Custom JWT Validation
Implement custom validation logic:
public class CustomJwtValidator : ISecurityTokenValidator
{
private readonly JwtSecurityTokenHandler _handler;
private readonly ILogger<CustomJwtValidator> _logger;
public CustomJwtValidator(ILogger<CustomJwtValidator> logger)
{
_handler = new JwtSecurityTokenHandler();
_logger = logger;
}
public bool CanValidateToken => true;
public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
public bool CanReadToken(string securityToken)
{
return _handler.CanReadToken(securityToken);
}
public ClaimsPrincipal ValidateToken(
string securityToken,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
try
{
var principal = _handler.ValidateToken(
securityToken, validationParameters, out validatedToken);
// Custom validation logic
var userId = principal.FindFirst("sub")?.Value;
if (string.IsNullOrEmpty(userId))
{
throw new SecurityTokenValidationException("Missing user ID claim");
}
// Check custom claims
var role = principal.FindFirst("role")?.Value;
if (role != "admin" && role != "user")
{
throw new SecurityTokenValidationException("Invalid role");
}
return principal;
}
catch (Exception ex)
{
_logger.LogError(ex, "JWT validation failed");
throw;
}
}
}
OAuth2 Authentication
Authorization Code Flow
Implement OAuth2 authorization code flow:
public class OAuth2AuthenticationHandler
{
private readonly HttpClient _httpClient;
private readonly OAuth2Config _config;
public string GetAuthorizationUrl(string state, string redirectUri)
{
var parameters = new Dictionary<string, string>
{
["client_id"] = _config.ClientId,
["response_type"] = "code",
["redirect_uri"] = redirectUri,
["scope"] = string.Join(" ", _config.Scopes),
["state"] = state
};
var queryString = string.Join("&",
parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
return $"{_config.AuthorizationEndpoint}?{queryString}";
}
public async Task<TokenResponse> ExchangeCodeForTokenAsync(
string authorizationCode,
string redirectUri)
{
var request = new HttpRequestMessage(HttpMethod.Post, _config.TokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = authorizationCode,
["redirect_uri"] = redirectUri,
["client_id"] = _config.ClientId,
["client_secret"] = _config.ClientSecret
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TokenResponse>(content);
}
}
Client Credentials Flow
Service-to-service authentication:
public class ClientCredentialsFlow
{
private readonly HttpClient _httpClient;
private readonly OAuth2Config _config;
public async Task<TokenResponse> GetAccessTokenAsync()
{
var request = new HttpRequestMessage(HttpMethod.Post, _config.TokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = _config.ClientId,
["client_secret"] = _config.ClientSecret,
["scope"] = string.Join(" ", _config.Scopes)
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TokenResponse>(content);
}
}
Token Refresh
Implement token refresh logic:
public class TokenRefreshService
{
private readonly ITokenStore _tokenStore;
private readonly OAuth2AuthenticationHandler _oauthHandler;
private readonly ILogger<TokenRefreshService> _logger;
public async Task<string> GetValidAccessTokenAsync(string userId)
{
var token = await _tokenStore.GetTokenAsync(userId);
if (token == null)
{
throw new InvalidOperationException("No token found");
}
// Check if token needs refresh (5 minutes before expiry)
if (DateTime.UtcNow >= token.ExpiresAt.AddMinutes(-5))
{
_logger.LogInformation("Refreshing token for user {UserId}", userId);
var newToken = await _oauthHandler.RefreshTokenAsync(token.RefreshToken);
await _tokenStore.SaveTokenAsync(userId, newToken);
return newToken.AccessToken;
}
return token.AccessToken;
}
}
API Key Authentication
API Key Validation
Implement API key validation:
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private readonly IApiKeyValidator _apiKeyValidator;
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("X-API-Key", out var apiKeyHeader))
{
return AuthenticateResult.Fail("Missing API key");
}
var apiKey = apiKeyHeader.ToString();
var client = await _apiKeyValidator.ValidateApiKeyAsync(apiKey);
if (client == null)
{
return AuthenticateResult.Fail("Invalid API key");
}
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, client.ClientId),
new Claim(ClaimTypes.Name, client.ClientName),
new Claim("ApiKeyId", client.ApiKeyId)
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
API Key Store
Store and validate API keys:
public interface IApiKeyStore
{
Task<ApiKeyClient> GetClientByApiKeyAsync(string apiKey);
Task<bool> IsApiKeyValidAsync(string apiKey);
Task RevokeApiKeyAsync(string apiKey);
}
public class DatabaseApiKeyStore : IApiKeyStore
{
private readonly IDbContext _dbContext;
public async Task<ApiKeyClient> GetClientByApiKeyAsync(string apiKey)
{
var hash = HashApiKey(apiKey);
var client = await _dbContext.ApiKeys
.Where(k => k.KeyHash == hash && k.IsActive)
.Select(k => k.Client)
.FirstOrDefaultAsync();
return client;
}
private string HashApiKey(string apiKey)
{
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(apiKey));
return Convert.ToBase64String(hash);
}
}
Service-to-Service Authentication
Mutual TLS (mTLS)
Implement mTLS for service authentication:
public class MutualTlsHandler : DelegatingHandler
{
private readonly X509Certificate2 _clientCertificate;
public MutualTlsHandler(X509Certificate2 clientCertificate)
{
_clientCertificate = clientCertificate;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var handler = new HttpClientHandler
{
ClientCertificates = { _clientCertificate },
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
// Validate server certificate
return ValidateServerCertificate(cert, chain, errors);
}
};
using var httpClient = new HttpClient(handler);
return await httpClient.SendAsync(request, cancellationToken);
}
}
Service Account Tokens
Use service account tokens:
public class ServiceAccountAuthentication
{
private readonly ITokenService _tokenService;
private readonly IConfiguration _configuration;
public async Task<string> GetServiceTokenAsync()
{
var serviceAccount = _configuration["ServiceAccount:Id"];
var secret = _configuration["ServiceAccount:Secret"];
return await _tokenService.GetServiceTokenAsync(serviceAccount, secret);
}
}
Forwarding Authentication
Forward User Claims
Forward authentication to backend services:
public class ForwardAuthMiddleware
{
private readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext context)
{
if (context.User.Identity.IsAuthenticated)
{
// Forward user ID
context.Request.Headers.Add("X-User-Id",
context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value);
// Forward user claims
var claims = context.User.Claims
.Select(c => $"{c.Type}={c.Value}");
context.Request.Headers.Add("X-User-Claims",
string.Join(",", claims));
// Forward roles
var roles = context.User.FindAll(ClaimTypes.Role)
.Select(c => c.Value);
context.Request.Headers.Add("X-User-Roles",
string.Join(",", roles));
}
await _next(context);
}
}
Best Practices
1. Validate Tokens Properly
- Validate issuer, audience, and expiration
- Check signature
- Verify claims
- Handle clock skew
2. Secure Token Storage
- Never store tokens in localStorage for web apps
- Use httpOnly cookies when possible
- Encrypt tokens at rest
- Implement token rotation
3. Handle Token Refresh
- Refresh before expiration
- Handle refresh failures
- Implement retry logic
- Log refresh events
4. Monitor Authentication
- Track authentication failures
- Monitor token usage
- Alert on suspicious patterns
- Log all auth events
5. Implement Rate Limiting
- Limit authentication attempts
- Prevent brute force attacks
- Throttle token requests
- Monitor for abuse
Advanced Authentication Patterns
Token Introspection
Validate tokens via introspection endpoint:
public class TokenIntrospectionService
{
public async Task<TokenInfo> IntrospectTokenAsync(string token)
{
var request = new HttpRequestMessage(HttpMethod.Post, _introspectionEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["token"] = token,
["token_type_hint"] = "access_token"
})
};
request.Headers.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{_clientId}:{_clientSecret}")));
var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TokenInfo>(content);
}
}
Multi-Tenant Authentication
Handle authentication for multiple tenants:
public class MultiTenantAuthHandler
{
public async Task<ClaimsPrincipal> AuthenticateAsync(HttpContext context)
{
var tenantId = ExtractTenantId(context);
var authConfig = await GetAuthConfigForTenantAsync(tenantId);
return await AuthenticateWithConfigAsync(context, authConfig);
}
}
Real-World Scenarios
Scenario 1: Service Mesh Integration
Integrate with service mesh authentication:
public class ServiceMeshAuthHandler
{
public async Task ForwardAuthToMeshAsync(HttpContext context)
{
// Extract mTLS certificate
var certificate = context.Connection.ClientCertificate;
// Forward to service mesh
context.Request.Headers.Add("X-Client-Cert",
Convert.ToBase64String(certificate.RawData));
}
}
Extended FAQ
Q: How do I handle token expiration at the gateway?
A: Implement automatic refresh:
public async Task<T> CallWithAutoRefreshAsync<T>(Func<string, Task<T>> apiCall)
{
var token = await GetValidTokenAsync();
try
{
return await apiCall(token);
}
catch (UnauthorizedException)
{
token = await RefreshTokenAsync();
return await apiCall(token);
}
}
Conclusion
Implementing proper authentication at the API gateway is essential for securing microservices. By understanding JWT validation, OAuth2 flows, API keys, and service-to-service authentication, you can build secure, production-ready API gateways that protect your backend services.
Key Takeaways:
- Use JWT for stateless auth - Good for microservices
- Implement OAuth2 properly - Handle all grant types
- Secure API keys - Hash and validate properly
- Forward authentication - Pass user context to backends
- Monitor authentication - Track failures and usage
- Handle token refresh - Implement proper refresh logic
- Token introspection - Validate tokens remotely
- Multi-tenant support - Handle multiple tenants
- Service mesh integration - mTLS support
- Automatic refresh - Seamless token management
Next Steps:
- Choose authentication method
- Implement token validation
- Set up token refresh
- Configure forwarding
- Set up monitoring
- Implement introspection
- Add multi-tenant support
Authentication Implementation Guide
Step-by-Step Implementation
-
Choose Authentication Method
- JWT for stateless auth
- OAuth 2.0 for delegated access
- API keys for service-to-service
- mTLS for high security
-
Implement Token Validation
- Validate token signature
- Check token expiration
- Verify token issuer
- Validate token audience
-
Set Up Token Refresh
- Handle refresh tokens
- Implement automatic refresh
- Cache refreshed tokens
- Handle refresh failures
-
Configure Forwarding
- Forward user context
- Add custom headers
- Preserve authentication info
- Handle token propagation
-
Set Up Monitoring
- Track authentication failures
- Monitor token usage
- Alert on anomalies
- Generate audit logs
Authentication Patterns
JWT Validation
public class JwtValidator
{
public async Task<ClaimsPrincipal> ValidateJwtAsync(string token)
{
var handler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = await GetSigningKeyAsync()
};
return handler.ValidateToken(token, validationParameters, out _);
}
}
OAuth 2.0 Token Refresh
public class OAuthTokenRefresher
{
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
{
var request = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken,
["client_id"] = _clientId,
["client_secret"] = _clientSecret
})
};
var response = await _httpClient.SendAsync(request);
return await response.Content.ReadFromJsonAsync<TokenResponse>();
}
}
Security Best Practices
Token Storage
- Never store tokens in localStorage
- Use httpOnly cookies for web apps
- Encrypt tokens at rest
- Implement token rotation
- Monitor token usage
Token Validation
- Always validate token signature
- Check token expiration
- Verify token issuer
- Validate token audience
- Check token revocation
Authentication Configuration Examples
Ocelot JWT Configuration
Configure JWT in Ocelot:
{
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": []
},
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "api.example.com",
"Port": 443
}
],
"UpstreamPathTemplate": "/api/{everything}",
"UpstreamHttpMethod": [ "GET", "POST" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": []
}
}
]
}
YARP Authentication
Configure authentication in YARP:
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://identity.example.com";
options.Audience = "api.example.com";
});
app.UseAuthentication();
app.UseAuthorization();
app.MapReverseProxy()
.RequireAuthorization();
Kong OAuth Configuration
Configure OAuth in Kong:
plugins:
- name: oauth2
config:
scopes:
- read
- write
mandatory_scope: true
token_expiration: 3600
Token Management
Token Caching
Cache tokens for performance:
public class TokenCache
{
private readonly IMemoryCache _cache;
public async Task<string> GetTokenAsync(string clientId)
{
var cacheKey = $"token:{clientId}";
if (_cache.TryGetValue(cacheKey, out string cached))
{
return cached;
}
var token = await GetNewTokenAsync(clientId);
_cache.Set(cacheKey, token, TimeSpan.FromMinutes(55));
return token;
}
}
Authentication Best Practices
Token Validation Pipeline
Implement comprehensive token validation:
public class TokenValidationPipeline
{
public async Task<ValidationResult> ValidateTokenAsync(string token)
{
// Step 1: Parse token
var parsed = ParseToken(token);
if (parsed == null)
{
return ValidationResult.Invalid("Invalid token format");
}
// Step 2: Validate signature
if (!await ValidateSignatureAsync(parsed))
{
return ValidationResult.Invalid("Invalid token signature");
}
// Step 3: Check expiration
if (IsExpired(parsed))
{
return ValidationResult.Expired("Token expired");
}
// Step 4: Check revocation
if (await IsRevokedAsync(parsed))
{
return ValidationResult.Revoked("Token revoked");
}
// Step 5: Validate claims
if (!ValidateClaims(parsed))
{
return ValidationResult.Invalid("Invalid token claims");
}
return ValidationResult.Valid(parsed);
}
}
Authentication Middleware
Implement authentication middleware:
public class AuthenticationMiddleware
{
public async Task InvokeAsync(HttpContext context)
{
var token = ExtractToken(context);
if (string.IsNullOrEmpty(token))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized");
return;
}
var validationResult = await _tokenValidator.ValidateTokenAsync(token);
if (!validationResult.IsValid)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync(validationResult.Error);
return;
}
context.User = validationResult.Principal;
await _next(context);
}
}
OAuth 2.0 Implementation
Authorization Code Flow
Implement authorization code flow:
public class OAuth2AuthorizationCodeFlow
{
public async Task<string> GetAuthorizationUrlAsync(string clientId, string redirectUri)
{
var parameters = new Dictionary<string, string>
{
["response_type"] = "code",
["client_id"] = clientId,
["redirect_uri"] = redirectUri,
["scope"] = "read write",
["state"] = GenerateState()
};
var queryString = string.Join("&",
parameters.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
return $"{_authorizationEndpoint}?{queryString}";
}
public async Task<TokenResponse> ExchangeCodeForTokenAsync(
string code,
string redirectUri)
{
var request = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = code,
["redirect_uri"] = redirectUri,
["client_id"] = _clientId,
["client_secret"] = _clientSecret
})
};
var response = await _httpClient.SendAsync(request);
return await response.Content.ReadFromJsonAsync<TokenResponse>();
}
}
Client Credentials Flow
Implement client credentials flow:
public class OAuth2ClientCredentialsFlow
{
public async Task<TokenResponse> GetTokenAsync()
{
var request = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = _clientId,
["client_secret"] = _clientSecret,
["scope"] = "api.read api.write"
})
};
var response = await _httpClient.SendAsync(request);
return await response.Content.ReadFromJsonAsync<TokenResponse>();
}
}
Token Introspection
Token Introspection Implementation
Implement token introspection:
public class TokenIntrospection
{
public async Task<IntrospectionResult> IntrospectTokenAsync(string token)
{
var request = new HttpRequestMessage(HttpMethod.Post, _introspectionEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["token"] = token,
["token_type_hint"] = "access_token",
["client_id"] = _clientId,
["client_secret"] = _clientSecret
})
};
var response = await _httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<IntrospectionResult>();
return result;
}
}
Authentication Best Practices Summary
Implementation Checklist
- Choose authentication method (JWT, OAuth 2.0, API keys)
- Implement token validation
- Set up token refresh
- Configure token caching
- Enable audit logging
- Set up monitoring
- Test authentication flows
- Document authentication procedures
- Review security settings
- Plan for token rotation
Production Deployment
Before deploying authentication:
- Test all authentication flows
- Verify token validation works
- Test token refresh
- Validate error handling
- Set up monitoring
- Load test authentication
- Document procedures
- Review security settings
Authentication Troubleshooting
Common Issues
Troubleshoot authentication issues:
public class AuthenticationTroubleshooter
{
public async Task<DiagnosticsResult> DiagnoseAsync(string token)
{
var diagnostics = new DiagnosticsResult();
// Check token format
diagnostics.TokenFormatValid = ValidateTokenFormat(token);
// Check token signature
diagnostics.SignatureValid = await ValidateSignatureAsync(token);
// Check token expiration
diagnostics.NotExpired = CheckExpiration(token);
// Check token revocation
diagnostics.NotRevoked = await CheckRevocationAsync(token);
return diagnostics;
}
}
Authentication Implementation Examples
JWT Validation Service
Complete JWT validation:
public class JwtValidationService
{
private readonly IConfiguration _configuration;
private readonly ILogger<JwtValidationService> _logger;
public async Task<ClaimsPrincipal> ValidateTokenAsync(string token)
{
var handler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = _configuration["Jwt:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = await GetSigningKeyAsync(),
ClockSkew = TimeSpan.Zero
};
try
{
var principal = handler.ValidateToken(token, validationParameters, out _);
return principal;
}
catch (SecurityTokenExpiredException)
{
_logger.LogWarning("Token expired");
throw;
}
catch (SecurityTokenInvalidSignatureException)
{
_logger.LogWarning("Invalid token signature");
throw;
}
}
}
OAuth 2.0 Client
Complete OAuth 2.0 client:
public class OAuth2Client
{
private readonly HttpClient _httpClient;
private readonly OAuth2Config _config;
public async Task<TokenResponse> GetTokenAsync(string code, string redirectUri)
{
var request = new HttpRequestMessage(HttpMethod.Post, _config.TokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = code,
["redirect_uri"] = redirectUri,
["client_id"] = _config.ClientId,
["client_secret"] = _config.ClientSecret
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TokenResponse>();
}
}
Authentication Token Management
Token Refresh Strategy
Implement token refresh:
public class TokenRefreshStrategy
{
private readonly ITokenCache _tokenCache;
private readonly ILogger<TokenRefreshStrategy> _logger;
public async Task<string> GetValidTokenAsync(string userId)
{
var cachedToken = await _tokenCache.GetTokenAsync(userId);
if (cachedToken != null && !IsTokenExpiringSoon(cachedToken))
{
return cachedToken.AccessToken;
}
// Refresh token
var refreshedToken = await RefreshTokenAsync(cachedToken?.RefreshToken);
await _tokenCache.SetTokenAsync(userId, refreshedToken);
return refreshedToken.AccessToken;
}
private bool IsTokenExpiringSoon(TokenInfo token)
{
var expirationTime = token.ExpiresAt;
var timeUntilExpiration = expirationTime - DateTime.UtcNow;
return timeUntilExpiration < TimeSpan.FromMinutes(5);
}
}
Token Revocation
Handle token revocation:
public class TokenRevocationService
{
public async Task RevokeTokenAsync(string token)
{
// Add to revocation list
await AddToRevocationListAsync(token);
// Invalidate cache
await InvalidateTokenCacheAsync(token);
// Notify clients
await NotifyClientsAsync(token);
}
public async Task<bool> IsTokenRevokedAsync(string token)
{
return await IsInRevocationListAsync(token);
}
}
Authentication Security Hardening
Token Validation Hardening
Harden token validation:
public class HardenedTokenValidator
{
public async Task<ValidationResult> ValidateTokenHardenedAsync(string token)
{
// Check revocation
if (await IsTokenRevokedAsync(token))
{
return new ValidationResult { IsValid = false, Reason = "Token revoked" };
}
// Check expiration
if (IsTokenExpired(token))
{
return new ValidationResult { IsValid = false, Reason = "Token expired" };
}
// Check signature
if (!await ValidateTokenSignatureAsync(token))
{
return new ValidationResult { IsValid = false, Reason = "Invalid signature" };
}
// Check issuer
if (!ValidateTokenIssuer(token))
{
return new ValidationResult { IsValid = false, Reason = "Invalid issuer" };
}
// Check audience
if (!ValidateTokenAudience(token))
{
return new ValidationResult { IsValid = false, Reason = "Invalid audience" };
}
return new ValidationResult { IsValid = true };
}
}
Authentication Rate Limiting
Limit authentication attempts:
public class AuthenticationRateLimiter
{
private readonly IRateLimiter _rateLimiter;
public async Task<bool> CheckAuthenticationRateLimitAsync(string identifier)
{
var key = $"auth:ratelimit:{identifier}";
return await _rateLimiter.CheckLimitAsync(key, 5, TimeSpan.FromMinutes(15));
}
}
Authentication Best Practices Summary
Key Takeaways
- Use JWT for Stateless Authentication: Ideal for microservices and distributed systems
- Implement OAuth 2.0 for Third-Party Integration: Standard protocol for secure authorization
- Always Validate Tokens: Check signature, expiration, issuer, and audience
- Implement Token Refresh: Seamless user experience with automatic token renewal
- Monitor Authentication: Track authentication failures, token usage, and security events
Security Considerations
- Always use HTTPS for token transmission
- Implement proper token storage (secure cookies or memory)
- Validate tokens on every request
- Implement token revocation for security incidents
- Use short-lived access tokens with longer refresh tokens
- Monitor for suspicious authentication patterns
Common Pitfalls to Avoid
- Storing tokens in localStorage (XSS vulnerability)
- Not validating token signatures
- Using long-lived tokens without refresh mechanism
- Not implementing token revocation
- Failing to monitor authentication events
Authentication Implementation Checklist
Pre-Implementation
- Choose authentication method (JWT, OAuth 2.0, API keys)
- Design token structure and claims
- Plan token expiration and refresh strategy
- Select token storage mechanism
- Design error handling and security measures
Implementation
- Implement token generation/validation
- Add authentication middleware
- Implement token refresh mechanism
- Add token revocation support
- Configure security headers
Post-Implementation
- Monitor authentication metrics
- Review security logs regularly
- Test token expiration and refresh
- Update documentation
- Train team on authentication patterns
Authentication Troubleshooting Guide
Common Issues
Issue 1: Token validation failures
- Symptom: Valid tokens being rejected
- Solution: Check token signature, expiration, issuer, audience
- Prevention: Implement comprehensive token validation
Issue 2: Token refresh not working
- Symptom: Users getting logged out unexpectedly
- Solution: Check refresh token validity and expiration
- Prevention: Implement proper refresh token handling
Issue 3: Security vulnerabilities
- Symptom: Tokens being compromised
- Solution: Implement token revocation, use HTTPS, secure storage
- Prevention: Follow security best practices from the start
For more API gateway guidance, explore our API Gateway Comparison or Rate Limiting Strategies.