Wednesday, 20 November 2024

Refresh Token : JWT Token Authentication WEBAPI

 To implement JWT (JSON Web Token) refresh token logic in a Web API, the general idea is to issue two tokens when a user successfully logs in:

  1. Access Token (short-lived): This token is used for authenticating requests and is typically valid for a short duration (e.g., 15 minutes).
  2. Refresh Token (long-lived): This token is used to get a new access token when the old one expires. It typically has a longer expiry time (e.g., 7 days or more).

Steps to Implement JWT Refresh Token in a Web API

1. Login Endpoint (Generate Access and Refresh Tokens)

When the user successfully logs in, generate both an access token and a refresh token. The access token will be used for authentication, and the refresh token will be stored securely (typically in an HttpOnly cookie or in the database).

public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginRequest request)
    {
        // Validate user credentials
        var user = AuthenticateUser(request.Username, request.Password);
        if (user == null) return Unauthorized();

        // Generate JWT Access Token
        var accessToken = GenerateAccessToken(user);

        // Generate Refresh Token
        var refreshToken = GenerateRefreshToken();

        // Save the refresh token in the database or cache associated with the user

        return Ok(new
        {
            AccessToken = accessToken,
            RefreshToken = refreshToken
        });
    }

    private string GenerateAccessToken(User user)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, user.Username),
            new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString())
            // Add other claims as necessary
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
_configuration["Jwt:SecretKey"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddMinutes(15), // short-lived access token
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    private string GenerateRefreshToken()
    {
        var randomNumber = new byte[32];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
        }

        return Convert.ToBase64String(randomNumber);
    }
}

2. Refresh Token Endpoint

When the access token expires, the client sends the refresh token to this endpoint to get a new access token. The refresh token is validated, and if valid, a new access token is issued.

[HttpPost("refresh-token")]
public IActionResult RefreshToken([FromBody] RefreshTokenRequest request)
{
    // Validate the refresh token
    var user = ValidateRefreshToken(request.RefreshToken);
    if (user == null) return Unauthorized();

    // Generate new access token
    var accessToken = GenerateAccessToken(user);

    // Optionally, generate a new refresh token and save it (if you are rotating
// refresh tokens)
    var refreshToken = GenerateRefreshToken();
    SaveRefreshToken(user, refreshToken);

    return Ok(new
    {
        AccessToken = accessToken,
        RefreshToken = refreshToken
    });
}

private User ValidateRefreshToken(string refreshToken)
{
    // Validate the refresh token. Check if it's valid and matches what's stored
// in the database.
    // This could involve checking a database, cache, or other storage for a
// matching refresh token.
    // Implement this logic based on your storage method return GetUserByRefreshToken(refreshToken);
}

3. Refresh Token Storage

Refresh tokens should be securely stored. Options include:

  • HttpOnly Cookies: Secure and less vulnerable to XSS attacks.
  • Database: Store refresh tokens in a table or cache, mapping them to user accounts and ensuring the refresh token is revoked after use.

If you're storing the refresh token in a cookie, set the HttpOnly flag and Secure flag to ensure the token is sent only over HTTPS and cannot be accessed via JavaScript.

Example for setting refresh token in HttpOnly cookie:

Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict,
        Expires = DateTime.Now.AddDays(7) // Set expiry to match your policy
    });
   

4. Token Expiry and Invalidating Refresh Tokens

Make sure that:

  • Access tokens expire quickly (e.g., in 15 minutes).
  • Refresh tokens can either have a long expiration time (e.g., 7 days) or a single-use (rotating refresh tokens) mechanism to increase security.
  • When a refresh token is used, it’s either revoked or replaced with a new refresh token to avoid token reuse.

5. Middleware for Token Validation

The access token is included in request headers (typically in the Authorization header) as a bearer token. This token is validated with each request to ensure it is still valid.

public class JwtMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;

    public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
    {
        _next = next;
        _configuration = configuration;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var token = context.Request.Headers["Authorization"].FirstOrDefault()?
.Split(" ").Last();
        if (token != null)
        {
            AttachUserToContext(context, token);
        }

        await _next(context);
    }

    private void AttachUserToContext(HttpContext context, string token)
    {
        try
        {
            var claimsPrincipal = ValidateToken(token);
            context.User = claimsPrincipal;
        }
        catch
        {
            context.User = null;
        }
    }

    private ClaimsPrincipal ValidateToken(string token)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
_configuration["Jwt:SecretKey"]));
        var tokenHandler = new JwtSecurityTokenHandler();

        var validationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            IssuerSigningKey = key,
            ClockSkew = TimeSpan.Zero,
            ValidIssuer = _configuration["Jwt:Issuer"],
            ValidAudience = _configuration["Jwt:Audience"]
        };

        return tokenHandler.ValidateToken(token, validationParameters, out _);
    }
}

Key Considerations:

  • Secure Storage: Always ensure that refresh tokens are stored securely, either in a database or HttpOnly cookies.
  • Token Rotation: Optionally, rotate refresh tokens to increase security (i.e., issue a new refresh token each time a new access token is issued).
  • Revocation: Implement a strategy for revoking refresh tokens if necessary (e.g., after logout or password change).

This setup should give you a basic implementation of JWT with access token and refresh token in your Web API.


0 comments:

Post a Comment

Topics

ADFS (1) ADO .Net (1) Ajax (1) Angular (47) Angular Js (15) ASP .Net (14) Authentication (4) Azure (3) Breeze.js (1) C# (55) CD (1) CI (2) CloudComputing (2) Coding (10) CQRS (1) CSS (2) Design_Pattern (7) DevOps (4) DI (3) Dotnet (10) DotnetCore (20) Entity Framework (5) ExpressJS (4) Html (4) IIS (1) Javascript (17) Jquery (8) jwtToken (4) Lamda (3) Linq (10) microservice (4) Mongodb (1) MVC (46) NodeJS (8) React (10) SDLC (1) Sql Server (32) SSIS (3) SSO (1) TypeScript (3) UI (1) UnitTest (2) WCF (14) Web Api (16) Web Service (1) XMl (1)

Dotnet Guru Archives