File: BearerTokenHandler.cs
Web Access
Project: src\src\Security\Authentication\BearerToken\src\Microsoft.AspNetCore.Authentication.BearerToken.csproj (Microsoft.AspNetCore.Authentication.BearerToken)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
 
namespace Microsoft.AspNetCore.Authentication.BearerToken;
 
internal sealed class BearerTokenHandler(IOptionsMonitor<BearerTokenOptions> optionsMonitor, ILoggerFactory loggerFactory, UrlEncoder urlEncoder)
    : SignInAuthenticationHandler<BearerTokenOptions>(optionsMonitor, loggerFactory, urlEncoder)
{
    private static readonly AuthenticateResult FailedUnprotectingToken = AuthenticateResult.Fail("Unprotected token failed");
    private static readonly AuthenticateResult TokenExpired = AuthenticateResult.Fail("Token expired");
 
    private new BearerTokenEvents Events => (BearerTokenEvents)base.Events!;
 
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Give application opportunity to find from a different location, adjust, or reject token.
        var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
 
        await Events.MessageReceivedAsync(messageReceivedContext);
 
        if (messageReceivedContext.Result is not null)
        {
            return messageReceivedContext.Result;
        }
 
        var token = messageReceivedContext.Token ?? GetBearerTokenOrNull();
 
        if (token is null)
        {
            return AuthenticateResult.NoResult();
        }
 
        var ticket = Options.BearerTokenProtector.Unprotect(token);
 
        if (ticket?.Properties?.ExpiresUtc is not { } expiresUtc)
        {
            return FailedUnprotectingToken;
        }
 
        if (TimeProvider.GetUtcNow() >= expiresUtc)
        {
            return TokenExpired;
        }
 
        return AuthenticateResult.Success(ticket);
    }
 
    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.Headers.Append(HeaderNames.WWWAuthenticate, "Bearer");
        return base.HandleChallengeAsync(properties);
    }
 
    protected override async Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
    {
        var utcNow = TimeProvider.GetUtcNow();
 
        properties ??= new();
        properties.ExpiresUtc = utcNow + Options.BearerTokenExpiration;
 
        var response = new AccessTokenResponse
        {
            AccessToken = Options.BearerTokenProtector.Protect(CreateBearerTicket(user, properties)),
            ExpiresIn = (long)Options.BearerTokenExpiration.TotalSeconds,
            RefreshToken = Options.RefreshTokenProtector.Protect(CreateRefreshTicket(user, utcNow)),
        };
 
        Logger.AuthenticationSchemeSignedIn(Scheme.Name);
 
        await Context.Response.WriteAsJsonAsync(response, ResolveAccessTokenJsonTypeInfo(Context));
    }
 
    private static JsonTypeInfo<AccessTokenResponse> ResolveAccessTokenJsonTypeInfo(HttpContext httpContext)
    {
        // Attempt to resolve options from DI then fall back to static options
        var typeInfo = httpContext.RequestServices.GetService<IOptions<JsonOptions>>()
            ?.Value?.SerializerOptions?.GetTypeInfo(typeof(AccessTokenResponse)) as JsonTypeInfo<AccessTokenResponse>;
        return typeInfo ?? BearerTokenJsonSerializerContext.Default.AccessTokenResponse;
    }
 
    // No-op to avoid interfering with any mass sign-out logic.
    protected override Task HandleSignOutAsync(AuthenticationProperties? properties) => Task.CompletedTask;
 
    private string? GetBearerTokenOrNull()
    {
        var authorization = Request.Headers.Authorization.ToString();
 
        return authorization.StartsWith("Bearer ", StringComparison.Ordinal)
            ? authorization["Bearer ".Length..]
            : null;
    }
 
    private AuthenticationTicket CreateBearerTicket(ClaimsPrincipal user, AuthenticationProperties properties)
        => new(user, properties, $"{Scheme.Name}:AccessToken");
 
    private AuthenticationTicket CreateRefreshTicket(ClaimsPrincipal user, DateTimeOffset utcNow)
    {
        var refreshProperties = new AuthenticationProperties
        {
            ExpiresUtc = utcNow + Options.RefreshTokenExpiration
        };
 
        return new AuthenticationTicket(user, refreshProperties, $"{Scheme.Name}:RefreshToken");
    }
}