JWT Bearer Authentication

AuthenticationJWTBearerASP.NET Core.NETMiddlewareOpenID Connect

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 and overrides the HandleAuthenticateAsync() method to perform JWT processing. This handler is responsible for JSON Web Token deserialization, validation, and creating appropriate AuthenticateResult and AuthenticationTicket.

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

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;
            }
        };
    });