JWT Bearer Authentication
Library
JWT Bearer Authentication
Overview
JWT Bearer Authentication is Microsoft's official middleware library for implementing JSON Web Token (JWT) based authentication in ASP.NET Core applications.
Details
JWT Bearer Authentication is provided as the Microsoft.AspNetCore.Authentication.JwtBearer package, a middleware library that supports JWT-based authentication in ASP.NET Core and Azure environments. This library enables receiving OpenID Connect Bearer tokens and provides secure authentication for APIs and web services.
Key features include automatic JWT token validation, support for multiple encryption algorithms, integration with OpenID Connect protocols, custom authentication event handling, and complete integration with Azure cloud services. Particularly, the Authority URI functionality automatically retrieves and validates public keys required for token signature verification, meeting enterprise-level security requirements.
The JwtBearerHandler inherits from AuthenticationHandler
The library is compatible with .NET Core 3.0 and later and .NET Standard 2.1, featuring seamless ASP.NET Core application integration, secure API and web service authentication, flexible token validation parameter configuration, and compliance with high security standards.
Pros and Cons
Pros
- Official Microsoft Support: Reliability and support through official library
- ASP.NET Core Integration: Complete integration with the framework
- Automatic Token Validation: Automatic JWT signature validation and security checks
- OpenID Connect Support: Complete compatibility with standard protocols
- Azure Integration: Seamless integration with Azure Active Directory
- Flexible Configuration: Rich configuration options and customizability
- High Performance: Optimized middleware implementation
Cons
- .NET Limited: Only available in .NET/ASP.NET Core environments
- Learning Cost: Requires understanding of JWT specifications and ASP.NET Core authentication system
- Configuration Complexity: Complexity when setting up advanced security configurations
- Debug Difficulty: Difficult to identify causes of authentication errors
- Microsoft Dependency: Dependency on Microsoft ecosystem
- HTTPS Required: HTTPS requirement in production environments
Key Links
- NuGet Package
- Microsoft Docs - JWT Bearer
- ASP.NET Core Authentication
- JWT.io
- Azure Active Directory
- OpenID Connect
Usage Examples
Basic Setup
<!-- Package Installation -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
# CLI Installation
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package System.IdentityModel.Tokens.Jwt
Basic JWT Configuration (Program.cs)
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// JWT Authentication Configuration
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"])
),
ClockSkew = TimeSpan.Zero // Disable token time skew
};
});
builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
// Add authentication middleware (order is important)
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
appsettings.json Configuration
{
"Jwt": {
"SecretKey": "your-super-secret-key-that-is-at-least-256-bits-long",
"Issuer": "your-app-issuer",
"Audience": "your-app-audience",
"ExpirationInMinutes": 60
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
JWT Generation Service
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public interface IJwtTokenService
{
string GenerateToken(string userId, string email, IList<string> roles);
ClaimsPrincipal ValidateToken(string token);
}
public class JwtTokenService : IJwtTokenService
{
private readonly IConfiguration _configuration;
private readonly SymmetricSecurityKey _securityKey;
public JwtTokenService(IConfiguration configuration)
{
_configuration = configuration;
_securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])
);
}
public string GenerateToken(string userId, string email, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Email, email),
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat,
new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds().ToString(),
ClaimValueTypes.Integer64)
};
// Add roles to claims
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var credentials = new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256);
var expiration = DateTime.UtcNow.AddMinutes(
int.Parse(_configuration["Jwt:ExpirationInMinutes"])
);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: expiration,
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public ClaimsPrincipal ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
IssuerSigningKey = _securityKey,
ClockSkew = TimeSpan.Zero
};
SecurityToken validatedToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
return principal;
}
}
Authentication Controller
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IJwtTokenService _jwtTokenService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public AuthController(
IJwtTokenService jwtTokenService,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_jwtTokenService = jwtTokenService;
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user == null)
{
return Unauthorized(new { message = "Invalid credentials" });
}
var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
if (!result.Succeeded)
{
return Unauthorized(new { message = "Invalid credentials" });
}
var roles = await _userManager.GetRolesAsync(user);
var token = _jwtTokenService.GenerateToken(user.Id, user.Email, roles);
return Ok(new LoginResponse
{
Token = token,
ExpiresAt = DateTime.UtcNow.AddMinutes(60),
User = new UserInfo
{
Id = user.Id,
Email = user.Email,
Name = user.UserName,
Roles = roles
}
});
}
[HttpPost("refresh")]
[Authorize]
public async Task<IActionResult> RefreshToken()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return Unauthorized();
}
var roles = await _userManager.GetRolesAsync(user);
var newToken = _jwtTokenService.GenerateToken(user.Id, user.Email, roles);
return Ok(new { token = newToken, expiresAt = DateTime.UtcNow.AddMinutes(60) });
}
[HttpGet("me")]
[Authorize]
public async Task<IActionResult> GetCurrentUser()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound();
}
var roles = await _userManager.GetRolesAsync(user);
return Ok(new UserInfo
{
Id = user.Id,
Email = user.Email,
Name = user.UserName,
Roles = roles
});
}
}
public class LoginRequest
{
public string Email { get; set; }
public string Password { get; set; }
}
public class LoginResponse
{
public string Token { get; set; }
public DateTime ExpiresAt { get; set; }
public UserInfo User { get; set; }
}
public class UserInfo
{
public string Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public IList<string> Roles { get; set; }
}
Protected API Controller
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
[ApiController]
[Route("api/[controller]")]
[Authorize] // JWT authentication required
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var userEmail = User.FindFirst(ClaimTypes.Email)?.Value;
// User-specific data retrieval logic
var products = GetUserProducts(userId);
return Ok(products);
}
[HttpPost]
[Authorize(Roles = "Admin,Manager")] // Specific roles required
public IActionResult CreateProduct([FromBody] ProductCreateRequest request)
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// Product creation logic
var product = CreateNewProduct(request, userId);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = GetProductById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")] // Admin only
public IActionResult DeleteProduct(int id)
{
var success = DeleteProductById(id);
if (!success)
{
return NotFound();
}
return NoContent();
}
private List<Product> GetUserProducts(string userId)
{
// Implementation example
return new List<Product>();
}
private Product CreateNewProduct(ProductCreateRequest request, string userId)
{
// Implementation example
return new Product { Id = 1, Name = request.Name };
}
private Product GetProductById(int id)
{
// Implementation example
return new Product { Id = id, Name = "Sample Product" };
}
private bool DeleteProductById(int id)
{
// Implementation example
return true;
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
public class ProductCreateRequest
{
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
Azure AD Integration
// Azure AD integration Program.cs configuration
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/{tenant-id}";
options.Audience = builder.Configuration["AzureAd:ClientId"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero
};
// Custom event handling
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
// Processing after token validation
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
var userId = claimsIdentity?.FindFirst("sub")?.Value;
// Add custom claims, etc.
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
// Processing on authentication failure
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
var response = new { message = "Authentication failed" };
return context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(response));
},
OnChallenge = context =>
{
// Processing on authentication challenge
context.HandleResponse();
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
var response = new { message = "Authentication required" };
return context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(response));
}
};
});
Custom Authentication Middleware
public class CustomJwtMiddleware
{
private readonly RequestDelegate _next;
private readonly IJwtTokenService _jwtTokenService;
public CustomJwtMiddleware(RequestDelegate next, IJwtTokenService jwtTokenService)
{
_next = next;
_jwtTokenService = jwtTokenService;
}
public async Task InvokeAsync(HttpContext context)
{
var token = ExtractTokenFromHeader(context.Request);
if (!string.IsNullOrEmpty(token))
{
try
{
var principal = _jwtTokenService.ValidateToken(token);
context.User = principal;
}
catch (Exception ex)
{
// Log recording
// Continue processing even if token is invalid (allow anonymous access)
}
}
await _next(context);
}
private string ExtractTokenFromHeader(HttpRequest request)
{
var authHeader = request.Headers["Authorization"].FirstOrDefault();
if (authHeader != null && authHeader.StartsWith("Bearer "))
{
return authHeader.Substring("Bearer ".Length).Trim();
}
return null;
}
}
// Middleware registration
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<CustomJwtMiddleware>();
// Other middleware configuration
}
Advanced Token Validation Settings
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// Basic validation parameters
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// Issuer and audience configuration
ValidIssuer = configuration["Jwt:Issuer"],
ValidAudience = configuration["Jwt:Audience"],
// Signing key configuration
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])
),
// Advanced settings
ClockSkew = TimeSpan.Zero, // Disable time skew
RequireExpirationTime = true, // Expiration time required
RequireSignedTokens = true, // Signature required
// Custom validation
LifetimeValidator = (notBefore, expires, token, validationParameters) =>
{
return expires != null && expires > DateTime.UtcNow;
},
// Claim transformation
NameClaimType = ClaimTypes.NameIdentifier,
RoleClaimType = ClaimTypes.Role
};
// HTTPS redirect settings (production environment)
options.RequireHttpsMetadata = !env.IsDevelopment();
// Custom token retrieval
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Retrieve token from cookie
var token = context.Request.Cookies["access_token"];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.CompletedTask;
}
};
});