Design Stateless APIs
Build scalable APIs without server-side state.
By EMEPublished: February 20, 2025
statelessscalabilityjwthorizontal scaling
A Simple Analogy
Stateless APIs are like a grocery clerk with note-taking customers. Customers bring all info they need; clerk doesn't need to remember anyone.
Why Stateless?
- Scalability: Add servers without coordination
- Reliability: Server failure doesn't lose state
- Performance: No state lookup overhead
- Distribution: Works with load balancers
- Simplicity: No session management
Token-Based Authentication
// Generate token
public class AuthService
{
public string GenerateToken(User user)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role)
};
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddHours(24),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
// Validate token
[Authorize]
[HttpGet("profile")]
public IActionResult GetProfile()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return Ok(new { UserId = userId });
}
Request Context
// All info in request/response
[HttpGet("orders/{id}")]
public async Task<IActionResult> GetOrder(int id)
{
// Get user from token claims
var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
// Query database (no session lookup)
var order = await _db.Orders
.FirstOrDefaultAsync(o => o.Id == id && o.UserId == userId);
if (order == null)
return NotFound();
return Ok(order);
}
Caching Tokens
public class TokenCacheService
{
private readonly IDistributedCache _cache;
public async Task CacheTokenAsync(string token, string userId, TimeSpan expiration)
{
await _cache.SetStringAsync(
$"token:{token}",
userId,
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiration }
);
}
public async Task<bool> IsValidTokenAsync(string token)
{
var userId = await _cache.GetStringAsync($"token:{token}");
return userId != null;
}
public async Task RevokeTokenAsync(string token)
{
await _cache.RemoveAsync($"token:{token}");
}
}
Best Practices
- Tokens: Use JWT or similar standards
- Short-lived: Access tokens expire quickly
- Refresh: Use refresh tokens for long sessions
- Immutable: Don't rely on modifying server state
- Distributed: Work across multiple servers
Related Concepts
- JWT tokens
- OAuth 2.0
- Load balancing
- Microservices
Summary
Build stateless APIs using token-based authentication. All user context travels in the token, enabling horizontal scaling.