Isaac.

JSON Web Tokens (JWT) Advanced

Master JWT claims, validation, and practical implementation patterns.

By EMEPublished: February 20, 2025
jwtauthenticationtokenssecurityclaims

A Simple Analogy

JWT is like a tamper-proof ID badge. It contains information (claims) and is cryptographically signed so it can't be faked. Bouncers verify the badge's signature without calling the issuing office.


What Are JWT Claims?

JWTs contain claims—structured data about the token holder. Standard claims include sub (subject), exp (expiration), iat (issued at). Custom claims add application-specific data.


Why Master JWT?

  • Stateless: No server session storage needed
  • Scalable: Works across load-balanced servers
  • Mobile-friendly: Perfect for mobile apps
  • Interoperable: Standard format works everywhere

JWT Structure

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWxpY2UiLCJpYXQiOjE2ODA5NjcwNjB9.signature

Header.Payload.Signature

Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "123", "name": "Alice", "iat": 1680967060}
Signature: HMAC-SHA256(header.payload, secret)

Standard Claims

| Claim | Meaning | Example | |-------|---------|---------| | sub | Subject (user ID) | "user-123" | | exp | Expiration time | 1735689600 | | iat | Issued at | 1735686000 | | iss | Issuer | "auth-server" | | aud | Audience | "api" |


.NET JWT Implementation

// Install: System.IdentityModel.Tokens.Jwt

public class TokenService
{
    private readonly string _secret;
    
    public string GenerateToken(User user)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_secret);
        
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Name),
                new Claim(ClaimTypes.Email, user.Email),
                new Claim("role", user.Role),  // Custom claim
                new Claim("department", user.Department)  // Custom claim
            }),
            Expires = DateTime.UtcNow.AddHours(24),
            Issuer = "myapp",
            Audience = "myapp-api",
            SigningCredentials = new SigningCredentials(
                new SymmetricSecurityKey(key),
                SecurityAlgorithms.HmacSha256Signature
            )
        };
        
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
    
    public ClaimsPrincipal ValidateToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_secret);
        
        try
        {
            var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = true,
                ValidIssuer = "myapp",
                ValidateAudience = true,
                ValidAudience = "myapp-api",
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);
            
            return principal;
        }
        catch
        {
            return null;
        }
    }
}

ASP.NET Core Bearer Token Middleware

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Secret"])),
        ValidateIssuer = true,
        ValidIssuer = "myapp",
        ValidateAudience = true,
        ValidAudience = "myapp-api",
        ValidateLifetime = true
    };
});

builder.Services.AddAuthorization();

[Authorize]
[HttpGet]
public IActionResult GetProfile()
{
    var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    var role = User.FindFirst("role")?.Value;
    
    return Ok(new { userId, role });
}

Refresh Token Pattern

public class TokenResponse
{
    public string AccessToken { get; set; }
    public string RefreshToken { get; set; }
    public int ExpiresIn { get; set; }
}

public TokenResponse LoginUser(User user)
{
    var accessToken = GenerateAccessToken(user);  // 15 minutes
    var refreshToken = GenerateRefreshToken(user);  // 7 days
    
    // Store refresh token in database
    _tokenRepository.SaveRefreshToken(user.Id, refreshToken);
    
    return new TokenResponse
    {
        AccessToken = accessToken,
        RefreshToken = refreshToken,
        ExpiresIn = 900  // 15 minutes in seconds
    };
}

[HttpPost("refresh")]
public TokenResponse RefreshToken([FromBody] string refreshToken)
{
    var principal = ValidateRefreshToken(refreshToken);
    if (principal == null)
        throw new UnauthorizedAccessException();
    
    var userId = int.Parse(principal.FindFirst(ClaimTypes.NameIdentifier).Value);
    var user = _userRepository.GetUser(userId);
    
    return LoginUser(user);
}

Best Practices

  1. Sign with strong key: Use secure random secret
  2. Use HTTPS: Prevent token interception
  3. Short expiry: Balance security and user experience
  4. Refresh tokens: Rotate access tokens regularly
  5. Include essential claims: Don't add sensitive data

Related Concepts to Explore

  • RS256 (asymmetric signing)
  • Token revocation strategies
  • Role-based and claim-based authorization
  • OAuth 2.0 integration
  • Token storage in SPAs

Summary

JWTs provide stateless, scalable authentication with claims-based authorization. Master token generation, validation, and refresh patterns to build secure authentication systems.