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

如何在对接Web API的ASP.NET Core MVC/Razor应用中优化JWT认证授权?

Great question! For your ASP.NET Core MVC/Razor Page app that relies on a backend API for JWT tokens, you absolutely don't need to manually handle token checks, refreshes, or sign-outs—ASP.NET Core's built-in authentication middleware can automate most of this, and it's far more maintainable than custom manual logic. Let's break down the optimal approach, plus how to implement custom auth mechanisms.

Optimal JWT Token Management with ASP.NET Core Authentication Middleware

Instead of reinventing the wheel, leverage the framework's built-in tools to handle token lifecycle automatically. Here's how to set it up for cookie-stored JWTs:

First, register the cookie authentication service in Program.cs, and hook into its events to handle token expiration and refresh seamlessly:

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
    // Secure cookie settings to block XSS/CSRF attacks
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Strict;

    options.Events = new CookieAuthenticationEvents
    {
        // Validate token on every request
        OnValidatePrincipal = async context =>
        {
            var accessToken = context.Properties.GetTokenValue("access_token");
            var refreshToken = context.Properties.GetTokenValue("refresh_token");

            if (string.IsNullOrEmpty(accessToken) || IsTokenExpired(accessToken))
            {
                // Attempt to refresh the access token via your API
                var newTokens = await RefreshTokensAsync(refreshToken, context.HttpContext.RequestServices);
                if (newTokens != null)
                {
                    // Update tokens stored in the cookie
                    context.Properties.UpdateTokenValue("access_token", newTokens.AccessToken);
                    context.Properties.UpdateTokenValue("refresh_token", newTokens.RefreshToken);
                    // Extend the cookie session
                    context.ShouldRenew = true;
                }
                else
                {
                    // Refresh failed—sign out the user
                    context.RejectPrincipal();
                    await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                }
            }
        },
        // Clean up tokens on sign-out
        OnSigningOut = async context =>
        {
            var refreshToken = context.Properties.GetTokenValue("refresh_token");
            // Call your API to revoke the refresh token (if supported)
            await RevokeRefreshTokenAsync(refreshToken, context.HttpContext.RequestServices);
        }
    };
});

Helper Methods for Token Handling

Add these utility functions to check token expiration and interact with your API:

private bool IsTokenExpired(string accessToken)
{
    var handler = new JwtSecurityTokenHandler();
    if (!handler.CanReadToken(accessToken)) return true;
    
    var jwtToken = handler.ReadJwtToken(accessToken);
    return jwtToken.ValidTo < DateTime.UtcNow;
}

private async Task<TokensDto> RefreshTokensAsync(string refreshToken, IServiceProvider services)
{
    var httpClient = services.GetRequiredService<IHttpClientFactory>().CreateClient("ApiClient");
    var response = await httpClient.PostAsync("/api/auth/refresh", 
        JsonContent.Create(new { RefreshToken = refreshToken }));
    
    return response.IsSuccessStatusCode ? 
        await response.Content.ReadFromJsonAsync<TokensDto>() : 
        null;
}

private async Task RevokeRefreshTokenAsync(string refreshToken, IServiceProvider services)
{
    var httpClient = services.GetRequiredService<IHttpClientFactory>().CreateClient("ApiClient");
    await httpClient.PostAsync("/api/auth/revoke", 
        JsonContent.Create(new { RefreshToken = refreshToken }));
}

When your login call to the API succeeds, use SignInAsync to persist tokens in the cookie's authentication properties:

// After fetching tokens from your API
var claims = new List<Claim>
{
    new Claim(ClaimTypes.Name, user.UserName),
    // Add other claims parsed from the JWT or API response
    new Claim(ClaimTypes.Role, user.Role)
};

var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
    Tokens = new Dictionary<string, string>
    {
        { "access_token", apiResponse.AccessToken },
        { "refresh_token", apiResponse.RefreshToken }
    },
    // Match cookie expiration to your refresh token's lifespan
    ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7)
};

await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(identity),
    authProperties);

Implementing Custom Authentication & Authorization

If you need full control over auth flows, you can build custom components tailored to your needs:

Custom Authentication Scheme

Create a custom AuthenticationHandler to handle token retrieval, validation, and refresh entirely on your own:

public class CustomJwtCookieHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly IHttpClientFactory _httpClientFactory;

    public CustomJwtCookieHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock,
        IHttpClientFactory httpClientFactory)
        : base(options, logger, encoder, clock)
    {
        _httpClientFactory = httpClientFactory;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Get tokens directly from cookies
        var accessToken = Request.Cookies["access_token"];
        var refreshToken = Request.Cookies["refresh_token"];

        if (string.IsNullOrEmpty(accessToken))
            return AuthenticateResult.NoResult();

        // Validate access token via API
        if (!await IsAccessTokenValid(accessToken))
        {
            // Refresh token if possible
            var newTokens = await RefreshTokensAsync(refreshToken);
            if (newTokens == null)
                return AuthenticateResult.Fail("Invalid or expired tokens");

            // Update cookies with new tokens
            UpdateTokenCookies(newTokens);
            accessToken = newTokens.AccessToken;
        }

        // Create claims principal from JWT
        var jwtHandler = new JwtSecurityTokenHandler();
        var jwtToken = jwtHandler.ReadJwtToken(accessToken);
        var claims = jwtToken.Claims.ToList();
        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);

        return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
    }

    private async Task<bool> IsAccessTokenValid(string accessToken)
    {
        var client = _httpClientFactory.CreateClient("ApiClient");
        var response = await client.PostAsync("/api/auth/validate", 
            JsonContent.Create(new { Token = accessToken }));
        return response.IsSuccessStatusCode;
    }

    private async Task<TokensDto> RefreshTokensAsync(string refreshToken)
    {
        var client = _httpClientFactory.CreateClient("ApiClient");
        var response = await client.PostAsync("/api/auth/refresh", 
            JsonContent.Create(new { RefreshToken = refreshToken }));
        return response.IsSuccessStatusCode ? 
            await response.Content.ReadFromJsonAsync<TokensDto>() : 
            null;
    }

    private void UpdateTokenCookies(TokensDto tokens)
    {
        var cookieOptions = new CookieOptions
        {
            HttpOnly = true,
            Secure = true,
            SameSite = SameSiteMode.Strict,
            Expires = DateTime.UtcNow.AddDays(7)
        };

        Response.Cookies.Append("access_token", tokens.AccessToken, cookieOptions);
        Response.Cookies.Append("refresh_token", tokens.RefreshToken, cookieOptions);
    }
}

Register the custom scheme in Program.cs:

builder.Services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, CustomJwtCookieHandler>("CustomJwtCookie", options => { });

// Set as default authorization policy
builder.Services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder("CustomJwtCookie")
        .RequireAuthenticatedUser()
        .Build();
});

Custom Authorization Policy

For granular access control, create a custom authorization requirement and handler:

// Custom requirement definition
public class RequireAdminRoleRequirement : IAuthorizationRequirement { }

// Handler to enforce the requirement
public class RequireAdminRoleHandler : AuthorizationHandler<RequireAdminRoleRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequireAdminRoleRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == "Admin"))
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
        return Task.CompletedTask;
    }
}

Register the handler and policy:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireAdmin", policy =>
        policy.Requirements.Add(new RequireAdminRoleRequirement()));
});

builder.Services.AddSingleton<IAuthorizationHandler, RequireAdminRoleHandler>();

Use the policy in controllers/Razor Pages:

[Authorize(Policy = "RequireAdmin")]
public IActionResult AdminDashboard()
{
    return View();
}

Key Best Practices
  • Secure Cookies: Always use HttpOnly, Secure, and SameSite=Strict for token cookies to mitigate XSS and CSRF attacks.
  • Token Rotation: When refreshing tokens, issue a new refresh token (and invalidate the old one) to reduce the risk of token theft.
  • Server-Side Token Revocation: Always call your API to revoke refresh tokens on sign-out to prevent unauthorized use.

内容的提问来源于stack exchange,提问作者Alyssa Green

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:14:39