Skip to content

When refresh token is expired the BFF should return 401, not 502 #380

@JJong-nl

Description

@JJong-nl

// Note: this handler can be called concurrently for the same user if multiple HTTP
// requests are processed in parallel: while this results in multiple refresh token
// requests being sent concurrently, this is something OpenIddict allows during a short
// period of time (called refresh token reuse leeway and set to 30 seconds by default).
var result = await _service.AuthenticateWithRefreshTokenAsync(new RefreshTokenAuthenticationRequest
{
CancellationToken = cancellationToken,
DisableUserInfo = true,
RefreshToken = GetRefreshToken(request.Options)
});
request.Headers.Authorization = new AuthenticationHeaderValue(Schemes.Bearer, result.AccessToken);
return new TokenRefreshingHttpResponseMessage(result, await base.SendAsync(request, cancellationToken));
}
// Otherwise, don't bother using the existing access token and refresh tokens immediately.
else
{
var result = await _service.AuthenticateWithRefreshTokenAsync(new RefreshTokenAuthenticationRequest
{
CancellationToken = cancellationToken,
DisableUserInfo = true,
RefreshToken = GetRefreshToken(request.Options)
});
request.Headers.Authorization = new AuthenticationHeaderValue(Schemes.Bearer, result.AccessToken);
return new TokenRefreshingHttpResponseMessage(result, await base.SendAsync(request, cancellationToken));
}

When _service.AuthenticateWithRefreshTokenAsync fails, it is returning an error.
I should expect to return a 401

solution is putting the AuthenticateWithRefreshTokenAsync within a try catch

            // If an access token expiration date was returned by the authorization server and stored
            // in the authentication cookie, use it to determine whether the token is about to expire.
            // If it's not, try to use it: if the resource server returns a 401 error response, try
            // to refresh the tokens before replaying the request with the new access token attached.
            var date = GetBackchannelAccessTokenExpirationDate(request.Options);
            if (date is null || TimeProvider.System.GetUtcNow() <= date?.AddMinutes(-5))
            {
                request.Headers.Authorization = new AuthenticationHeaderValue(Schemes.Bearer, GetBackchannelAccessToken(request.Options));

                var response = await base.SendAsync(request, cancellationToken);
                if (response.StatusCode is not HttpStatusCode.Unauthorized)
                {
                    return response;
                }

                // Note: this handler can be called concurrently for the same user if multiple HTTP
                // requests are processed in parallel: while this results in multiple refresh token
                // requests being sent concurrently, this is something OpenIddict allows during a short
                // period of time (called refresh token reuse leeway and set to 30 seconds by default).
                return await TryWithRefreshTokenAsync(request, cancellationToken);
            }

            // Otherwise, don't bother using the existing access token and refresh tokens immediately.
            else
            {
                return await TryWithRefreshTokenAsync(request, cancellationToken);
            }

            async Task<HttpResponseMessage> TryWithRefreshTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                try
                {
                    var result = await _service.AuthenticateWithRefreshTokenAsync(new RefreshTokenAuthenticationRequest
                    {
                        CancellationToken = cancellationToken,
                        DisableUserInfo = true,
                        RefreshToken = GetRefreshToken(request.Options)
                    });

                    request.Headers.Authorization = new AuthenticationHeaderValue(Schemes.Bearer, result.AccessToken);

                    return new TokenRefreshingHttpResponseMessage(result, await base.SendAsync(request, cancellationToken));
                }
                catch (Exception ex)
                {
                    // Refresh token failed, returning 401
                    var response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
                    {
                        RequestMessage = request
                    };
                    return response;

                }
            }

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions