// ------------------------------------------------------------------------------
// 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);
}
}
}
}