/// <summary> /// Performs the actiosn needed to process a request to compile the needed artifacts /// for performing authentication operations. /// </summary> /// /// <param name="request">The request to be processed.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="challengeGenerationRequired"><c>true</c> if the handler is required to be able to generate a challenge reponse; othewise, <c>false</c>.</param> /// <param name="authorizationnHeaderName">The name of the HTTP header expected to hold the authentication data; this will be used as the token key to the requested authentication scheme in the result.</param> /// /// <returns>The result of the processing.</returns> /// private static ProcessResult ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken, bool challengeGenerationRequired = false, string authorizationnHeaderName = HttpHeaders.Authorization) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var locator = request.GetDependencyScope(); // If there was a client certificate present, and processing does not require challenge generation, then attempt to locate a handler for client certificate // authentication and give it precedence. if ((!challengeGenerationRequired) && (request.GetClientCertificate() != null)) { var certHandler = OrderFulfillmentAuthenticateAttributeAttribute.SelectHandler(request, cancellationToken, locator, AuthenticationType.ClientCertificate.ToString(), challengeGenerationRequired); if (certHandler != null) { return(new ProcessResult(OrderFulfillmentAuthenticateAttributeAttribute.EmptyStringDictionary, certHandler)); } } // Attempt to locate the value of the HTTP authorization header. There may only be a single instance of the standard header, by spec // (see: http://tools.ietf.org/html/rfc7235#section-4.1) and the same semantics will be applied to any custom authorization headers that // may be used. IEnumerable <string> headerValues; // If a value was not available for the expected HTTP Authorization header, then a result cannot be determined. if ((cancellationToken.IsCancellationRequested) || (!request.Headers.TryGetValues(authorizationnHeaderName, out headerValues))) { return(null); } var headerValue = headerValues.FirstOrDefault(); if (String.IsNullOrEmpty(headerValue)) { return(null); } // Parse the authorization header into its composite tokens; if there was no entry for the header name, than the // desired authentiation scheme was not present in the header and a result cannot be determined. var parser = locator.GetService(typeof(IHttpHeaderParser)) as IHttpHeaderParser; if (parser == null) { throw new MissingDependencyException("An IHttpParser could not be located"); } var headerTokens = parser.ParseAuthorization(headerValue, authorizationnHeaderName); if (!headerTokens.ContainsKey(authorizationnHeaderName)) { return(null); } // If there was no available handler for the desired authentication scheme, a result cannot be determined. var handler = OrderFulfillmentAuthenticateAttributeAttribute.SelectHandler(request, cancellationToken, locator, headerTokens[authorizationnHeaderName], challengeGenerationRequired); if ((cancellationToken.IsCancellationRequested) || (handler == null)) { return(null); } return(new ProcessResult(headerTokens, handler)); }
/// <summary> /// Performs the actions needed to authenticate the request. /// </summary> /// /// <param name="context">The authentication context to consider.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// /// <returns>A Task that will perform the authentication challenge.</returns> /// public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var request = context.Request; var locator = request.GetDependencyScope(); var logger = (locator.GetService(typeof(ILogger)) as ILogger)?.WithCorrelationId(request?.GetOrderFulfillmentCorrelationId()); var certThumbprint = request.GetClientCertificate()?.Thumbprint; var body = await request.SafeReadContentAsStringAsync(); var result = OrderFulfillmentAuthenticateAttributeAttribute.ProcessRequest(request, cancellationToken); // If there was no available result after processing the request, authentication is not possible. if (result == null) { try { logger.Information($"Authentication is not possible because No authentication handler was available for {{Route}} with Headers: [{{Headers}}] { Environment.NewLine }Client Certificate: [{{ClientCertificateThumbprint}}]", request?.RequestUri, request?.Headers, certThumbprint); } catch { // Do nothing; logging is a non-critical operation that should not cause // cascading failures. } return; } // If the handler was able to produce a principal, then set it on the authentication context. This will indicate to the hosting // infrastructure that there is an authenticated entity. if (cancellationToken.IsCancellationRequested) { return; } var principal = result.AuthenticationHandler.Authenticate(result.AuthenticationTokens, context); if (principal != null) { context.Principal = principal; } else { try { logger.Information($"Unable to authenticate for {{Route}} with Headers: [{{Headers}}]{ Environment.NewLine }Client Certificate: [{{ClientCertificateThumbprint}}]", request?.RequestUri, request?.Headers, certThumbprint); } catch { // Do nothing; logging is a non-critical operation that should not cause // cascading failures. } } }
/// <summary> /// Performs the actions needed to add an authentication challenge to the inner IHttpActionResult, if needed. /// </summary> /// /// <param name="context">The authentication challenge context to consider.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// /// <returns>A Task that will perform the authentication challenge.</returns> /// /// <remarks> /// This method will be executed by the framework after the action has executed, regardless of whether or not a challenge /// should be generated. Before generating the challenge, determine if it /// </remarks> /// public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var request = context.Request; // If there was an authenticated principal associated with the request or there was a result generated that should not // overridden, then no challenge is needed. if ((cancellationToken.IsCancellationRequested) || (request.GetRequestContext()?.Principal != null) || (!OrderFulfillmentAuthenticateAttributeAttribute.ShouldOverrideResponse(context))) { return; } var result = OrderFulfillmentAuthenticateAttributeAttribute.ProcessRequest(request, cancellationToken, true); // If there was no available handler located after processing the request, then attempt to retrieve the // default challenge handler. if (result == null) { var handler = OrderFulfillmentAuthenticateAttributeAttribute.SelectDefaultChallengeHandler(request.GetDependencyScope()); if (handler != null) { result = new ProcessResult(OrderFulfillmentAuthenticateAttributeAttribute.EmptyStringDictionary, handler); } } // If there was no available challenge handler, authentication is not possible. Make // no alterations to the current response. if ((cancellationToken.IsCancellationRequested) || (result == null)) { return; } // If the handler was able to produce a challenge, then clear any existing result/response and // set the new Unauthorized with the correct challenge. var challenge = result.AuthenticationHandler.GenerateChallenge(result.AuthenticationTokens, context); if (challenge != null) { context.ActionContext.Response?.Dispose(); context.ActionContext.Response = null; context.Result = new UnauthorizedResult(new[] { challenge }, request); try { var locator = request.GetDependencyScope(); var logger = (locator.GetService(typeof(ILogger)) as ILogger); if (logger != null) { var body = await request.SafeReadContentAsStringAsync(); logger.WithCorrelationId(request?.GetOrderFulfillmentCorrelationId()) .Information($"Response: {{Response}} { Environment.NewLine } Authentication is needed; a challenge was issued for {{Route}} with Headers: [{{Headers}}]", HttpStatusCode.Unauthorized, request?.RequestUri, request?.Headers); } } catch { // Do nothing; logging is a non-critical operation that should not cause // cascading failures. } } }