JWT Bearer Token Authentication
Overview
JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. They are widely used for stateless authentication in modern web APIs.
JWT Structure
A JWT consists of three parts separated by dots (.):
header.payload.signature
Example JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload (Claims)
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "Admin",
"exp": 1735689600
}
3. Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Implementation in ASP.NET Core
1. Install Package
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
2. Configure JWT Authentication
var builder = WebApplication.CreateBuilder(args);
// Add JWT authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
3. appsettings.json
{
"Jwt": {
"SecretKey": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!",
"Issuer": "https://yourdomain.com",
"Audience": "https://yourdomain.com",
"ExpiryMinutes": 60
}
}
4. Generate JWT Token
public class TokenService
{
private readonly IConfiguration _configuration;
public TokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(string userId, string email, string role)
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(ClaimTypes.Role, role),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(
Convert.ToDouble(_configuration["Jwt:ExpiryMinutes"])),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
5. Login Endpoint
app.MapPost("/api/auth/login", async (LoginRequest request, TokenService tokenService) =>
{
// Validate credentials (check database)
if (request.Email == "admin@example.com" && request.Password == "password")
{
var token = tokenService.GenerateToken("1", request.Email, "Admin");
return Results.Ok(new { token });
}
return Results.Unauthorized();
});
public record LoginRequest(string Email, string Password);
6. Protect Endpoints
app.MapGet("/api/protected", () => "This is protected data")
.RequireAuthorization();
app.MapGet("/api/admin", () => "Admin only")
.RequireAuthorization(policy => policy.RequireRole("Admin"));
Refresh Tokens
Refresh tokens allow obtaining new access tokens without re-authentication.
Implementation
public class RefreshToken
{
public string Token { get; set; }
public DateTime Expires { get; set; }
public bool IsExpired => DateTime.UtcNow >= Expires;
public string UserId { get; set; }
}
public class TokenService
{
private readonly List<RefreshToken> _refreshTokens = new();
public (string accessToken, string refreshToken) GenerateTokens(string userId, string email, string role)
{
var accessToken = GenerateToken(userId, email, role);
var refreshToken = GenerateRefreshToken(userId);
return (accessToken, refreshToken);
}
private string GenerateRefreshToken(string userId)
{
var refreshToken = new RefreshToken
{
Token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)),
Expires = DateTime.UtcNow.AddDays(7),
UserId = userId
};
_refreshTokens.Add(refreshToken);
return refreshToken.Token;
}
public string RefreshAccessToken(string refreshToken)
{
var token = _refreshTokens.FirstOrDefault(t => t.Token == refreshToken);
if (token == null || token.IsExpired)
return null;
// Generate new access token
return GenerateToken(token.UserId, "user@example.com", "User");
}
}
Refresh Endpoint
app.MapPost("/api/auth/refresh", (RefreshTokenRequest request, TokenService tokenService) =>
{
var newAccessToken = tokenService.RefreshAccessToken(request.RefreshToken);
if (newAccessToken == null)
return Results.Unauthorized();
return Results.Ok(new { accessToken = newAccessToken });
});
public record RefreshTokenRequest(string RefreshToken);
Client Usage
Sending JWT in Requests
GET /api/protected HTTP/1.1
Host: localhost:5000
Authorization: Bearer eyJhbGci...
// JavaScript example
fetch('/api/protected', {
headers: {
'Authorization': `Bearer ${token}`
}
});
Best Practices
- Use HTTPS: Always use HTTPS in production
- Short expiry: Keep access token expiry short (15-60 minutes)
- Secure secrets: Store secret keys securely (Azure Key Vault, environment variables)
- Validate claims: Always validate issuer, audience, and expiration
- Use refresh tokens: Implement refresh tokens for better UX
- Token revocation: Implement token blacklisting for logout
Interview Questions
Q: What is a JWT and what are its three parts? A: JWT is a JSON Web Token consisting of Header (algorithm), Payload (claims), and Signature (verification).
Q: How do you secure JWT secret keys? A: Use environment variables, Azure Key Vault, or other secret management systems. Never hardcode or commit to source control.
Q: What's the difference between access tokens and refresh tokens? A: Access tokens are short-lived and used for API access. Refresh tokens are long-lived and used to obtain new access tokens without re-authentication.