File: Internal\AccessTokenHttpMessageHandler.cs
Web Access
Project: src\src\SignalR\clients\csharp\Http.Connections.Client\src\Microsoft.AspNetCore.Http.Connections.Client.csproj (Microsoft.AspNetCore.Http.Connections.Client)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
 
namespace Microsoft.AspNetCore.Http.Connections.Client.Internal;
 
internal sealed class AccessTokenHttpMessageHandler : DelegatingHandler
{
    private readonly HttpConnection _httpConnection;
    private string? _accessToken;
 
    public AccessTokenHttpMessageHandler(HttpMessageHandler inner, HttpConnection httpConnection) : base(inner)
    {
        _httpConnection = httpConnection;
    }
 
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var shouldRetry = true;
        if (string.IsNullOrEmpty(_accessToken) ||
            // Negotiate redirects likely will have a new access token so let's always grab a (potentially) new access token on negotiate
#if NET5_0_OR_GREATER
            request.Options.TryGetValue(new HttpRequestOptionsKey<bool>("IsNegotiate"), out var value) && value == true
#else
            request.Properties.TryGetValue("IsNegotiate", out var value) && value is true
#endif
            )
        {
            shouldRetry = false;
            _accessToken = await _httpConnection.GetAccessTokenAsync().ConfigureAwait(false);
        }
 
        SetAccessToken(_accessToken, request);
 
        var result = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        // retry once with a new token on auth failure
        if (shouldRetry && result.StatusCode is HttpStatusCode.Unauthorized)
        {
            HttpConnection.Log.RetryAccessToken(_httpConnection._logger, result.StatusCode);
            result.Dispose();
            _accessToken = await _httpConnection.GetAccessTokenAsync().ConfigureAwait(false);
 
            SetAccessToken(_accessToken, request);
 
            // Retrying the request relies on any HttpContent being non-disposable.
            // Currently this is true, the only HttpContent we send is type ReadOnlySequenceContent which is used by SSE and LongPolling for sending an already buffered byte[]
            result = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        }
        return result;
    }
 
    private static void SetAccessToken(string? accessToken, HttpRequestMessage request)
    {
        if (!string.IsNullOrEmpty(accessToken))
        {
            // Don't need to worry about WebSockets and browser because this code path will not be hit in the browser case
            // ClientWebSocketOptions.HttpVersion isn't settable in the browser
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        }
    }
}