Skip to main content

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

  1. Use HTTPS: Always use HTTPS in production
  2. Short expiry: Keep access token expiry short (15-60 minutes)
  3. Secure secrets: Store secret keys securely (Azure Key Vault, environment variables)
  4. Validate claims: Always validate issuer, audience, and expiration
  5. Use refresh tokens: Implement refresh tokens for better UX
  6. 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.

Resources