You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebAPI 2.0中JWT访问令牌过期时如何实现刷新令牌?

Implementing JWT Refresh Token Logic for ASP.NET WebAPI 2.0

Hey there! You’ve got a solid foundation with your basic JWT auth setup, but the missing piece here is a dual-token system to enable seamless token refreshes. Short-lived access tokens keep things secure, while longer-lived refresh tokens let clients get new access tokens without making users re-login (as long as their session is still valid). Let’s break down exactly how to implement this step by step.

First: What’s Missing in Your Current Setup?

Right now, you’re only issuing access tokens. If you set them to expire quickly (which you should for security), users get locked out frequently. If you set them to expire slowly, you increase the risk if a token gets stolen. The refresh token solves this balance—let’s fix that.


Server-Side Implementation Steps

1. Add a Refresh Token Storage System

Refresh tokens need to be tracked on the server (you can use a database, Redis, or even in-memory storage for testing—avoid in-memory for production though). At minimum, store these details:

  • The refresh token string itself
  • The associated user ID
  • Expiration date (way longer than access tokens, e.g., 7 days)
  • A flag to mark if the token is revoked (for logouts)

Here’s a simple model for this:

public class RefreshToken
{
    public int Id { get; set; }
    public string Token { get; set; }
    public string UserId { get; set; }
    public DateTime Expires { get; set; }
    public bool IsRevoked { get; set; }
}

2. Modify Your Login Endpoint to Issue Both Tokens

When a user logs in successfully, generate both an access token (short-lived, e.g., 15 minutes) and a refresh token (long-lived). Save the refresh token to your storage system, then return both tokens to the client.

You can reuse your existing JWT generation code—just add the refresh token logic:

[HttpPost]
[Route("api/auth/login")]
public IHttpActionResult Login(LoginModel model)
{
    // Validate user credentials (your existing logic here)
    var user = _userService.ValidateUser(model.Username, model.Password);
    if (user == null)
        return Unauthorized();

    // Generate short-lived access token
    var accessToken = GenerateAccessToken(user.Id, user.Username);
    // Generate long-lived refresh token
    var refreshToken = GenerateRefreshToken(user.Id);

    // Save refresh token to your database/storage
    _refreshTokenRepository.Save(refreshToken);

    return Ok(new
    {
        AccessToken = accessToken,
        RefreshToken = refreshToken.Token,
        ExpiresIn = 900 // 15 minutes in seconds
    });
}

// Reuse your existing access token generator
private string GenerateAccessToken(string userId, string username)
{
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["JwtSecret"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var claims = new[]
    {
        new Claim(ClaimTypes.NameIdentifier, userId),
        new Claim(ClaimTypes.Name, username)
    };

    var token = new JwtSecurityToken(
        issuer: ConfigurationManager.AppSettings["JwtIssuer"],
        audience: ConfigurationManager.AppSettings["JwtAudience"],
        claims: claims,
        expires: DateTime.UtcNow.AddMinutes(15),
        signingCredentials: creds
    );

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

// New method to generate refresh tokens
private RefreshToken GenerateRefreshToken(string userId)
{
    return new RefreshToken
    {
        Token = Guid.NewGuid().ToString("N"), // Use a secure random string
        UserId = userId,
        Expires = DateTime.UtcNow.AddDays(7),
        IsRevoked = false
    };
}

3. Add a Refresh Token Endpoint

This endpoint lets clients exchange a valid refresh token for a new access token (and optionally a new refresh token, for extra security). It needs to validate the refresh token first:

[HttpPost]
[Route("api/auth/refresh-token")]
public IHttpActionResult RefreshToken(RefreshTokenRequest model)
{
    // Look up the refresh token in storage
    var storedToken = _refreshTokenRepository.GetByToken(model.RefreshToken);
    
    // Validate the token: exists, not revoked, not expired
    if (storedToken == null || storedToken.IsRevoked || storedToken.Expires < DateTime.UtcNow)
        return Unauthorized();

    // Get the associated user
    var user = _userService.GetById(storedToken.UserId);
    if (user == null)
        return Unauthorized();

    // Generate new access token
    var newAccessToken = GenerateAccessToken(user.Id, user.Username);
    
    // Optional: Generate a new refresh token (revoke the old one)
    var newRefreshToken = GenerateRefreshToken(user.Id);
    storedToken.IsRevoked = true;
    _refreshTokenRepository.Update(storedToken);
    _refreshTokenRepository.Save(newRefreshToken);

    return Ok(new
    {
        AccessToken = newAccessToken,
        RefreshToken = newRefreshToken.Token,
        ExpiresIn = 900
    });
}

// Request model for the refresh endpoint
public class RefreshTokenRequest
{
    public string RefreshToken { get; set; }
}

4. Ensure JWT Auth is Configured Correctly

Double-check your WebApiConfig to make sure JWT auth is set up to return 401 when tokens are expired/invalid. Here’s a quick recap of the key config:

public static void Register(HttpConfiguration config)
{
    var jwtKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["JwtSecret"]));
    
    // Suppress default auth and add JWT bearer auth
    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

    var oAuthOptions = new OAuthAuthorizationServerOptions
    {
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(15),
        AccessTokenFormat = new JwtFormat(new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = ConfigurationManager.AppSettings["JwtIssuer"],
            ValidateAudience = true,
            ValidAudience = ConfigurationManager.AppSettings["JwtAudience"],
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = jwtKey,
            ClockSkew = TimeSpan.Zero // No grace period for expired tokens
        })
    };

    config.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
    {
        AccessTokenFormat = oAuthOptions.AccessTokenFormat
    });

    // Your other routing/config here
}

Client-Side Implementation (Web Forms & C# Desktop Apps)

Clients need to store both tokens (access token for API requests, refresh token for getting new access tokens) and handle 401 responses automatically.

C# Desktop App (HttpClient Example)

Wrap your HttpClient calls to handle token refreshes behind the scenes:

public class ApiClient
{
    private readonly HttpClient _httpClient;
    private string _accessToken;
    private string _refreshToken;

    public ApiClient(string baseApiUrl)
    {
        _httpClient = new HttpClient { BaseAddress = new Uri(baseApiUrl) };
    }

    // Login to get initial tokens
    public async Task<bool> Login(string username, string password)
    {
        var loginPayload = new { Username = username, Password = password };
        var response = await _httpClient.PostAsJsonAsync("/api/auth/login", loginPayload);
        
        if (!response.IsSuccessStatusCode) return false;
        
        var tokenResult = await response.Content.ReadAsAsync<TokenResponse>();
        _accessToken = tokenResult.AccessToken;
        _refreshToken = tokenResult.RefreshToken;
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
        return true;
    }

    // Generic request method with automatic token refresh
    public async Task<T> SendRequest<T>(HttpMethod method, string endpoint, object payload = null)
    {
        var request = new HttpRequestMessage(method, endpoint);
        if (payload != null)
            request.Content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");

        var response = await _httpClient.SendAsync(request);

        // Handle 401: try to refresh token
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            var refreshSuccess = await RefreshTokens();
            if (!refreshSuccess)
                throw new UnauthorizedAccessException("Session expired - please log in again.");

            // Retry the request with new token
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
            response = await _httpClient.SendAsync(request);
        }

        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsAsync<T>();
    }

    // Refresh tokens using stored refresh token
    private async Task<bool> RefreshTokens()
    {
        var refreshPayload = new { RefreshToken = _refreshToken };
        var response = await _httpClient.PostAsJsonAsync("/api/auth/refresh-token", refreshPayload);
        
        if (!response.IsSuccessStatusCode) return false;
        
        var tokenResult = await response.Content.ReadAsAsync<TokenResponse>();
        _accessToken = tokenResult.AccessToken;
        _refreshToken = tokenResult.RefreshToken;
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
        return true;
    }

    // Helper class for token responses
    private class TokenResponse
    {
        public string AccessToken { get; set; }
        public string RefreshToken { get; set; }
        public int ExpiresIn { get; set; }
    }
}

ASP.NET Web Forms Client

Store the tokens in Session (or HttpOnly Cookies for extra security) and create a similar helper class to handle requests. When you get a 401 response, call the refresh endpoint, update the stored tokens, and retry the original request.


Critical Security Notes

  • Never store refresh tokens client-side in plain text: Use encrypted storage (e.g., SecureStorage for desktop apps, HttpOnly Cookies for Web Forms).
  • Revoke refresh tokens on logout: When a user logs out, mark their refresh token as revoked in your storage system so it can’t be used again.
  • Limit refresh token lifespan: 7 days is a common balance—don’t make it too long.
  • Rate-limit the refresh endpoint: Prevent brute-force attacks by limiting how many times a client can call the refresh endpoint in a given time.
  • Rotate refresh tokens: Each time a client refreshes, issue a new refresh token and revoke the old one. This limits damage if a refresh token is stolen.

内容的提问来源于stack exchange,提问作者Neeraj Kumar Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:49:09