JSON Web Tokens (JWT) Advanced
Master JWT claims, validation, and practical implementation patterns.
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
- Sign with strong key: Use secure random secret
- Use HTTPS: Prevent token interception
- Short expiry: Balance security and user experience
- Refresh tokens: Rotate access tokens regularly
- 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.