Skip to main content
The Intent.AspNetCore.Identity.JWT module combines ASP.NET Core Identity with JWT (JSON Web Token) authentication, providing a complete authentication solution for modern Web APIs with token-based security.
This module bridges Intent.AspNetCore.Identity (user management) with Intent.Security.JWT (token-based auth) to provide a complete authentication system.

Overview

This module generates the necessary code to issue JWT tokens upon successful user authentication and validate those tokens on subsequent API requests. It provides a stateless authentication mechanism ideal for SPAs, mobile apps, and microservices.

Architecture

What Gets Generated

The module integrates Identity with JWT by generating:

Token Generation Service

public interface ITokenService
{
    Task<TokenResponse> GenerateTokenAsync(
        ApplicationUser user,
        CancellationToken cancellationToken = default);
}

public class TokenService : ITokenService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly JwtSettings _jwtSettings;

    public TokenService(
        UserManager<ApplicationUser> userManager,
        IOptions<JwtSettings> jwtSettings)
    {
        _userManager = userManager;
        _jwtSettings = jwtSettings.Value;
    }

    public async Task<TokenResponse> GenerateTokenAsync(
        ApplicationUser user,
        CancellationToken cancellationToken = default)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        // Add role claims
        var roles = await _userManager.GetRolesAsync(user);
        claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

        // Add user claims
        var userClaims = await _userManager.GetClaimsAsync(user);
        claims.AddRange(userClaims);

        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_jwtSettings.Secret));
        var credentials = new SigningCredentials(
            key,
            SecurityAlgorithms.HmacSha256);
        var expires = DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiryInMinutes);

        var token = new JwtSecurityToken(
            issuer: _jwtSettings.Issuer,
            audience: _jwtSettings.Audience,
            claims: claims,
            expires: expires,
            signingCredentials: credentials);

        return new TokenResponse
        {
            Token = new JwtSecurityTokenHandler().WriteToken(token),
            Expiry = expires
        };
    }
}

Login Endpoint

[HttpPost("login")]
[AllowAnonymous]
[ProducesResponseType(typeof(TokenResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<TokenResponse>> Login(
    [FromBody] LoginDto dto,
    CancellationToken cancellationToken)
{
    var user = await _userManager.FindByEmailAsync(dto.Email);
    if (user == null)
        return Unauthorized("Invalid credentials");

    var result = await _signInManager.CheckPasswordSignInAsync(
        user,
        dto.Password,
        lockoutOnFailure: true);

    if (!result.Succeeded)
    {
        if (result.IsLockedOut)
            return Unauthorized("Account is locked out");
        if (result.IsNotAllowed)
            return Unauthorized("Account is not allowed to sign in");
        
        return Unauthorized("Invalid credentials");
    }

    var tokenResponse = await _tokenService.GenerateTokenAsync(user, cancellationToken);
    return Ok(tokenResponse);
}

Key Features

Token Generation

Generate JWT tokens with user claims and roles

Automatic Validation

Validate tokens on every protected endpoint

Role Claims

Include user roles in JWT claims

Token Refresh

Support for refresh tokens and token renewal

JWT Configuration

Configure JWT settings in appsettings.json:
{
  "JwtSettings": {
    "Secret": "your-secret-key-min-32-characters-long",
    "Issuer": "https://yourdomain.com",
    "Audience": "https://yourdomain.com",
    "ExpiryInMinutes": 60
  }
}
Settings class:
public class JwtSettings
{
    public string Secret { get; set; }
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public int ExpiryInMinutes { get; set; }
}

Complete Authentication Flow

1. User Registration

[HttpPost("register")]
[AllowAnonymous]
public async Task<ActionResult<TokenResponse>> Register(
    [FromBody] RegisterDto dto,
    CancellationToken cancellationToken)
{
    var user = new ApplicationUser
    {
        UserName = dto.Email,
        Email = dto.Email,
        FirstName = dto.FirstName,
        LastName = dto.LastName
    };

    var result = await _userManager.CreateAsync(user, dto.Password);
    
    if (!result.Succeeded)
        return BadRequest(result.Errors);

    // Assign default role
    await _userManager.AddToRoleAsync(user, "User");

    // Generate token for immediate login
    var tokenResponse = await _tokenService.GenerateTokenAsync(user, cancellationToken);
    return Ok(tokenResponse);
}

2. Token Usage

Client includes token in requests:
GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

3. Protected Endpoints

[Authorize]
[HttpGet]
public async Task<ActionResult<List<OrderDto>>> GetMyOrders(
    CancellationToken cancellationToken)
{
    var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
    var orders = await _orderService.GetUserOrdersAsync(
        Guid.Parse(userId),
        cancellationToken);
    return Ok(orders);
}

[Authorize(Roles = "Admin")]
[HttpGet("all")]
public async Task<ActionResult<List<OrderDto>>> GetAllOrders(
    CancellationToken cancellationToken)
{
    var orders = await _orderService.GetAllOrdersAsync(cancellationToken);
    return Ok(orders);
}

Token Refresh

Implement refresh token functionality:
public class RefreshTokenService : IRefreshTokenService
{
    private readonly IApplicationDbContext _dbContext;
    private readonly ITokenService _tokenService;

    public async Task<TokenResponse> RefreshTokenAsync(
        string refreshToken,
        CancellationToken cancellationToken)
    {
        var storedToken = await _dbContext.RefreshTokens
            .Include(x => x.User)
            .FirstOrDefaultAsync(
                x => x.Token == refreshToken && !x.IsRevoked,
                cancellationToken);

        if (storedToken == null || storedToken.ExpiryDate < DateTime.UtcNow)
            throw new UnauthorizedException("Invalid refresh token");

        // Generate new token
        var newToken = await _tokenService.GenerateTokenAsync(
            storedToken.User,
            cancellationToken);

        // Revoke old refresh token
        storedToken.IsRevoked = true;
        await _dbContext.SaveChangesAsync(cancellationToken);

        return newToken;
    }
}
Refresh token endpoint:
[HttpPost("refresh-token")]
[AllowAnonymous]
public async Task<ActionResult<TokenResponse>> RefreshToken(
    [FromBody] RefreshTokenDto dto,
    CancellationToken cancellationToken)
{
    try
    {
        var response = await _refreshTokenService.RefreshTokenAsync(
            dto.RefreshToken,
            cancellationToken);
        return Ok(response);
    }
    catch (UnauthorizedException)
    {
        return Unauthorized("Invalid refresh token");
    }
}

Claims-Based Authorization

Use claims for fine-grained authorization:
public class ClaimsService
{
    private readonly UserManager<ApplicationUser> _userManager;

    public async Task AddClaimAsync(Guid userId, string claimType, string claimValue)
    {
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new NotFoundException("User not found");

        await _userManager.AddClaimAsync(
            user,
            new Claim(claimType, claimValue));
    }
}
Authorize based on claims:
[Authorize(Policy = "CanEditOrders")]
[HttpPut("{id}")]
public async Task<ActionResult> UpdateOrder(
    [FromRoute] Guid id,
    [FromBody] UpdateOrderDto dto,
    CancellationToken cancellationToken)
{
    await _orderService.UpdateAsync(id, dto, cancellationToken);
    return NoContent();
}
Policy configuration:
services.AddAuthorization(options =>
{
    options.AddPolicy("CanEditOrders", policy =>
        policy.RequireClaim("Permission", "Orders.Edit"));
    
    options.AddPolicy("IsAdminOrManager", policy =>
        policy.RequireRole("Admin", "Manager"));
});

Token Validation

Extract user information from token:
public class TokenValidator
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public Guid GetCurrentUserId()
    {
        var userIdClaim = _httpContextAccessor.HttpContext?.User
            .FindFirstValue(ClaimTypes.NameIdentifier);
        
        if (string.IsNullOrEmpty(userIdClaim))
            throw new UnauthorizedException("User not authenticated");

        return Guid.Parse(userIdClaim);
    }

    public List<string> GetCurrentUserRoles()
    {
        return _httpContextAccessor.HttpContext?.User
            .FindAll(ClaimTypes.Role)
            .Select(c => c.Value)
            .ToList() ?? new List<string>();
    }

    public bool HasClaim(string claimType, string claimValue)
    {
        return _httpContextAccessor.HttpContext?.User
            .HasClaim(claimType, claimValue) ?? false;
    }
}

Security Best Practices

  • Use a strong, random secret key (minimum 32 characters)
  • Store in environment variables or Azure Key Vault
  • Never commit secrets to source control
  • Rotate keys periodically
  • Keep access tokens short-lived (15-60 minutes)
  • Use refresh tokens for longer sessions
  • Implement token revocation for logout
  • Always use HTTPS in production
  • Set secure cookie flags
  • Implement HSTS headers
  • Validate all claims on the server
  • Don’t trust client-side role checks
  • Use policy-based authorization

Client Integration

JavaScript/TypeScript

class AuthService {
  private token: string | null = null;

  async login(email: string, password: string): Promise<void> {
    const response = await fetch('https://api.example.com/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });

    if (!response.ok) {
      throw new Error('Login failed');
    }

    const data = await response.json();
    this.token = data.token;
    localStorage.setItem('token', data.token);
  }

  async fetchProtectedResource(): Promise<any> {
    const response = await fetch('https://api.example.com/api/orders', {
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json'
      }
    });

    return await response.json();
  }

  logout(): void {
    this.token = null;
    localStorage.removeItem('token');
  }
}

C# Client

public class ApiClient
{
    private readonly HttpClient _httpClient;
    private string _token;

    public async Task LoginAsync(string email, string password)
    {
        var request = new LoginDto { Email = email, Password = password };
        var response = await _httpClient.PostAsJsonAsync("auth/login", request);
        
        response.EnsureSuccessStatusCode();
        
        var tokenResponse = await response.Content.ReadFromJsonAsync<TokenResponse>();
        _token = tokenResponse.Token;
        _httpClient.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", _token);
    }

    public async Task<List<OrderDto>> GetOrdersAsync()
    {
        var response = await _httpClient.GetAsync("api/orders");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<List<OrderDto>>();
    }
}

Troubleshooting

  • Verify token is included in Authorization header
  • Check token hasn’t expired
  • Ensure secret key matches between token generation and validation
  • Verify issuer and audience settings
  • User is authenticated but lacks required role/claim
  • Check role assignments in database
  • Verify policy requirements
  • Check clock skew between servers
  • Verify token format (should start with “Bearer ”)
  • Ensure JWT middleware is configured before Authorization middleware

Installation

This module is installed automatically when you have:
Intent.AspNetCore.Identity
Intent.Security.JWT

Dependencies

  • Intent.AspNetCore.Identity
  • Intent.Security.JWT
  • Intent.Application.Identity

Next Steps

Security.JWT

Configure JWT bearer authentication

Identity

Learn about ASP.NET Core Identity

Swashbuckle

Add authentication to Swagger UI

Controllers

Protect your API endpoints

Build docs developers (and LLMs) love