// ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ namespace Microsoft.Graph { using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; /// /// A implementation using standard .NET libraries. /// public class AuthenticationHandler : DelegatingHandler { /// /// MaxRetry property for 401's /// private int MaxRetry { get; set; } = 1; /// /// AuthOption property /// internal AuthenticationHandlerOption AuthOption { get; set; } /// /// AuthenticationProvider property /// public IAuthenticationProvider AuthenticationProvider { get; set; } /// /// Construct a new /// An authentication provider to pass to for authenticating requests. /// /// An OPTIONAL to configure public AuthenticationHandler(IAuthenticationProvider authenticationProvider, AuthenticationHandlerOption authOption = null) { AuthenticationProvider = authenticationProvider; AuthOption = authOption ?? new AuthenticationHandlerOption(); } /// /// Construct a new /// /// An authentication provider to pass to for authenticating requests. /// A HTTP message handler to pass to the for sending requests. /// An OPTIONAL to configure public AuthenticationHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler, AuthenticationHandlerOption authOption = null) : this(authenticationProvider, authOption) { InnerHandler = innerHandler; AuthenticationProvider = authenticationProvider; } /// /// Checks HTTP response message status code if it's unauthorized (401) or not /// /// The to send. /// private bool IsUnauthorized(HttpResponseMessage httpResponseMessage) { return httpResponseMessage.StatusCode == HttpStatusCode.Unauthorized; } /// /// Retry sending HTTP request /// /// The to send. /// The to send. /// An authentication provider to pass to for authenticating requests. /// private async Task SendRetryAsync(HttpResponseMessage httpResponseMessage, IAuthenticationProvider authProvider, CancellationToken cancellationToken) { int retryAttempt = 0; while (retryAttempt < MaxRetry) { // general clone request with internal CloneAsync (see CloneAsync for details) extension method var newRequest = await httpResponseMessage.RequestMessage.CloneAsync(); // Authenticate request using AuthenticationProvider await authProvider.AuthenticateRequestAsync(newRequest); httpResponseMessage = await base.SendAsync(newRequest, cancellationToken); retryAttempt++; if (!IsUnauthorized(httpResponseMessage) || !newRequest.IsBuffered()) { // Re-issue the request to get a new access token return httpResponseMessage; } } return httpResponseMessage; } /// /// Sends a HTTP request and retries the request when the response is unauthorized. /// This can happen when a token from the cache expires between graph getting the request and the backend receiving the request /// /// The to send. /// The for the request. /// protected override async Task SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken) { AuthOption = httpRequestMessage.GetMiddlewareOption() ?? AuthOption; // If default auth provider is not set, use the option var authProvider = AuthOption.AuthenticationProvider ?? AuthenticationProvider; // Authenticate request using AuthenticationProvider if (authProvider != null) { await authProvider.AuthenticateRequestAsync(httpRequestMessage); HttpResponseMessage response = await base.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); // Check if response is a 401 & is not a streamed body (is buffered) if (IsUnauthorized(response) && httpRequestMessage.IsBuffered()) { // re-issue the request to get a new access token response = await SendRetryAsync(response, authProvider, cancellationToken); } return response; } else { // NOTE: In order to support HttpProvider, we'll skip authentication if no provider is set. // We will add this check once we re-write a new HttpProvider. return await base.SendAsync(httpRequestMessage, cancellationToken); } } } }