如何修改WebApi过期Refresh Token的响应状态码与返回体?
I'm working with a WebAPI where expired Refresh Tokens default to returning a 400 status code with the response body {"Error":"invalid_client"}. I want to modify both the status code and response body content for this scenario.
So far, I've added a header flag in my code like this:
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { AuthenticationTicket ticket; if (_refreshTokens.TryRemove(context.Token, out ticket)) { if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc.Value.LocalDateTime < DateTime.Now) { context.Response.Headers.Add("Expired", new string[] { "Yes" }); } context.SetTicket(ticket); } }
Could anyone help me figure out how to fully customize the response? Thanks!
Answer
Absolutely, you can fully customize the response—your current approach of adding a header is a good start, but we need to override the default response handling entirely. Here are two straightforward methods to achieve this:
Method 1: Build a Custom Response Directly in ReceiveAsync
When you detect an expired refresh token, you can skip the default middleware logic and write your own status code and response body directly. The key is to call context.HandleResponse() to signal that you've taken care of the response.
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { if (_refreshTokens.TryRemove(context.Token, out var ticket)) { // Check if the token is expired if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc.Value.LocalDateTime < DateTime.Now) { // Set a more semantically accurate status code (401 instead of 400) context.Response.StatusCode = StatusCodes.Status401Unauthorized; // Define the content type for your response context.Response.ContentType = "application/json"; // Create your custom response body var customResponse = new { Error = "refresh_token_expired", Message = "Your refresh token has expired. Please log in again to get a new token.", ExpiredAt = ticket.Properties.ExpiresUtc.Value.UtcDateTime }; // Serialize and write the response await context.Response.WriteAsync(JsonSerializer.Serialize(customResponse)); // Tell the middleware we've handled the response—prevents default 400 output context.HandleResponse(); return; } context.SetTicket(ticket); } else { // Optional: Customize response for invalid/non-existent tokens too context.Response.StatusCode = StatusCodes.Status400BadRequest; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonSerializer.Serialize(new { Error = "invalid_refresh_token", Message = "The provided refresh token is not valid." })); context.HandleResponse(); } }
Key Notes for This Method:
context.HandleResponse()is critical—it stops the default middleware from generating theinvalid_clientresponse.- Using 401 Unauthorized is more semantically correct for expired credentials than the default 400, but you can adjust the status code to fit your API's conventions.
- If you're using Newtonsoft.Json instead of System.Text.Json, replace
JsonSerializer.SerializewithJsonConvert.SerializeObject.
Method 2: Use a Custom OAuth2 Error Handler (For Identity/OAuth2 Setups)
If your project uses ASP.NET Core Identity or OAuth2 middleware, you can hook into authentication events to centralize error handling:
services.AddAuthentication(options => { // Your base authentication configuration here }) .AddJwtBearer(options => { options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { if (context.Exception is SecurityTokenExpiredException) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; context.Response.WriteAsync(JsonSerializer.Serialize(new { Error = "token_expired", Message = "Your authentication token has expired." })); } return Task.CompletedTask; } }; });
This works best for access token expiration, but for refresh token-specific handling, Method 1 is more direct since it operates directly in the token receive pipeline.
内容的提问来源于stack exchange,提问作者Jeremy Loh

