<commit_before>// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;

namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{
    /// <summary>
    /// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware.
    /// </summary>
    public class OpenIdConnectHandler : RemoteAuthenticationHandler<OpenIdConnectOptions>
    {
        private const string NonceProperty = "N";
        private const string UriSchemeDelimiter = "://";

        private const string InputTagFormat = @"<input type=""hidden"" name=""{0}"" value=""{1}"" />";
        private const string HtmlFormFormat = @"<!doctype html>
<html>
<head>
    <title>Please wait while you're being redirected to the identity provider</title>
</head>
<body>
    <form name=""form"" method=""post"" action=""{0}"">
        {1}
        <noscript>Click here to finish the process: <input type=""submit"" /></noscript>
    </form>
    <script>document.form.submit();</script>
</body>
</html>";

        private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();

        private OpenIdConnectConfiguration _configuration;

        protected HttpClient Backchannel { get; private set; }

        protected HtmlEncoder HtmlEncoder { get; private set; }

        public OpenIdConnectHandler(HttpClient backchannel, HtmlEncoder htmlEncoder)
        {
            Backchannel = backchannel;
            HtmlEncoder = htmlEncoder;
        }

        /// <summary>
        /// Handles Signout
        /// </summary>
        /// <returns></returns>
        protected override async Task HandleSignOutAsync(SignOutContext signout)
        {
            if (signout != null)
            {
                if (_configuration == null && Options.ConfigurationManager != null)
                {
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
                }

                var message = new OpenIdConnectMessage()
                {
                    IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty),
                };

                // Set End_Session_Endpoint in order:
                // 1. properties.Redirect
                // 2. Options.PostLogoutRedirectUri
                var properties = new AuthenticationProperties(signout.Properties);
                var logoutRedirectUri = properties.RedirectUri;
                if (!string.IsNullOrEmpty(logoutRedirectUri))
                {
                    // Relative to PathBase
                    if (logoutRedirectUri.StartsWith("/", StringComparison.Ordinal))
                    {
                        logoutRedirectUri = BuildRedirectUri(logoutRedirectUri);
                    }
                    message.PostLogoutRedirectUri = logoutRedirectUri;
                }
                else if (!string.IsNullOrEmpty(Options.PostLogoutRedirectUri))
                {
                    logoutRedirectUri = Options.PostLogoutRedirectUri;
                    // Relative to PathBase
                    if (logoutRedirectUri.StartsWith("/", StringComparison.Ordinal))
                    {
                        logoutRedirectUri = BuildRedirectUri(logoutRedirectUri);
                    }
                    message.PostLogoutRedirectUri = logoutRedirectUri;
                }

                var principal = await Context.Authentication.AuthenticateAsync(Options.SignInScheme);
                message.IdTokenHint = principal?.FindFirst(OpenIdConnectParameterNames.IdToken)?.Value;

                var redirectContext = new RedirectContext(Context, Options, properties)
                {
                    ProtocolMessage = message
                };

                await Options.Events.RedirectToEndSessionEndpoint(redirectContext);
                if (redirectContext.HandledResponse)
                {
                    Logger.LogDebug(1, "RedirectToEndSessionEndpoint.HandledResponse");
                    return;
                }
                else if (redirectContext.Skipped)
                {
                    Logger.LogDebug(2, "RedirectToEndSessionEndpoint.Skipped");
                    return;
                }

                message = redirectContext.ProtocolMessage;

                if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet)
                {
                    var redirectUri = message.CreateLogoutRequestUrl();
                    if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
                    {
                        Logger.LogWarning(3, "The query string for Logout is not a well-formed URI. Redirect URI: '{0}'.", redirectUri);
                    }

                    Response.Redirect(redirectUri);
                }
                else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost)
                {
                    var inputs = new StringBuilder();
                    foreach (var parameter in message.Parameters)
                    {
                        var name = HtmlEncoder.Encode(parameter.Key);
                        var value = HtmlEncoder.Encode(parameter.Value);

                        var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value);
                        inputs.AppendLine(input);
                    }

                    var issuer = HtmlEncoder.Encode(message.IssuerAddress);

                    var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs);
                    var buffer = Encoding.UTF8.GetBytes(content);

                    Response.ContentLength = buffer.Length;
                    Response.ContentType = "text/html;charset=UTF-8";

                    // Emit Cache-Control=no-cache to prevent client caching.
                    Response.Headers[HeaderNames.CacheControl] = "no-cache";
                    Response.Headers[HeaderNames.Pragma] = "no-cache";
                    Response.Headers[HeaderNames.Expires] = "-1";

                    await Response.Body.WriteAsync(buffer, 0, buffer.Length);
                }
            }
        }

        /// <summary>
        /// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
        /// </summary>
        /// <returns></returns>
        /// <remarks>Uses log id's OIDCH-0026 - OIDCH-0050, next num: 37</remarks>
        protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            Logger.LogTrace(4, "Entering {0}." + nameof(HandleUnauthorizedAsync), GetType());

            // order for local RedirectUri
            // 1. challenge.Properties.RedirectUri
            // 2. CurrentUri if RedirectUri is not set)
            var properties = new AuthenticationProperties(context.Properties);

            if (string.IsNullOrEmpty(properties.RedirectUri))
            {
                properties.RedirectUri = CurrentUri;
            }
            Logger.LogTrace(5, "Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.", properties.RedirectUri);

            if (_configuration == null && Options.ConfigurationManager != null)
            {
                _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
            }

            var message = new OpenIdConnectMessage
            {
                ClientId = Options.ClientId,
                IssuerAddress = _configuration?.AuthorizationEndpoint ?? string.Empty,
                RedirectUri = BuildRedirectUri(Options.CallbackPath),
                Resource = Options.Resource,
                ResponseType = Options.ResponseType,
                Scope = string.Join(" ", Options.Scope)
            };

            // Omitting the response_mode parameter when it already corresponds to the default
            // response_mode used for the specified response_type is recommended by the specifications.
            // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
            if (!string.Equals(Options.ResponseType, OpenIdConnectResponseTypes.Code, StringComparison.Ordinal) ||
                !string.Equals(Options.ResponseMode, OpenIdConnectResponseModes.Query, StringComparison.Ordinal))
            {
                message.ResponseMode = Options.ResponseMode;
            }

            if (Options.ProtocolValidator.RequireNonce)
            {
                message.Nonce = Options.ProtocolValidator.GenerateNonce();
                WriteNonceCookie(message.Nonce);
            }

            GenerateCorrelationId(properties);

            var redirectContext = new RedirectContext(Context, Options, properties)
            {
                ProtocolMessage = message
            };

            await Options.Events.RedirectToAuthenticationEndpoint(redirectContext);
            if (redirectContext.HandledResponse)
            {
                Logger.LogDebug(6, "RedirectToAuthenticationEndpoint.HandledResponse");
                return true;
            }
            else if (redirectContext.Skipped)
            {
                Logger.LogDebug(7, "RedirectToAuthenticationEndpoint.Skipped");
                return false;
            }

            message = redirectContext.ProtocolMessage;

            if (!string.IsNullOrEmpty(message.State))
            {
                properties.Items[OpenIdConnectDefaults.UserstatePropertiesKey] = message.State;
            }

            // When redeeming a 'code' for an AccessToken, this value is needed
            properties.Items.Add(OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, message.RedirectUri);

            message.State = Options.StateDataFormat.Protect(properties);

            if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet)
            {
                var redirectUri = message.CreateAuthenticationRequestUrl();
                if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
                {
                    Logger.LogWarning(9, "The redirect URI is not well-formed. The URI is: '{0}'.", redirectUri);
                }

                Response.Redirect(redirectUri);
                return true;
            }
            else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost)
            {
                var inputs = new StringBuilder();
                foreach (var parameter in message.Parameters)
                {
                    var name = HtmlEncoder.Encode(parameter.Key);
                    var value = HtmlEncoder.Encode(parameter.Value);

                    var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value);
                    inputs.AppendLine(input);
                }

                var issuer = HtmlEncoder.Encode(message.IssuerAddress);

                var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs);
                var buffer = Encoding.UTF8.GetBytes(content);

                Response.ContentLength = buffer.Length;
                Response.ContentType = "text/html;charset=UTF-8";

                // Emit Cache-Control=no-cache to prevent client caching.
                Response.Headers[HeaderNames.CacheControl] = "no-cache";
                Response.Headers[HeaderNames.Pragma] = "no-cache";
                Response.Headers[HeaderNames.Expires] = "-1";

                await Response.Body.WriteAsync(buffer, 0, buffer.Length);
                return true;
            }

            throw new NotImplementedException($"An unsupported authentication method has been configured: {Options.AuthenticationMethod}");
        }

        /// <summary>
        /// Invoked to process incoming OpenIdConnect messages.
        /// </summary>
        /// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
        /// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks>
        protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
        {
            Logger.LogTrace(10, "Entering: {0}." + nameof(HandleRemoteAuthenticateAsync), GetType());

            OpenIdConnectMessage message = null;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                message = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));

                // response_mode=query (explicit or not) and a response_type containing id_token
                // or token are not considered as a safe combination and MUST be rejected.
                // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
                if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken))
                {
                    if (Options.SkipUnrecognizedRequests)
                    {
                        // Not for us?
                        return AuthenticateResult.Skip();
                    }
                    return AuthenticateResult.Fail("An OpenID Connect response cannot contain an " +
                            "identity token or an access token when using response_mode=query");
                }
            }
            // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
            else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
              && !string.IsNullOrEmpty(Request.ContentType)
              // May have media/type; charset=utf-8, allow partial match.
              && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
              && Request.Body.CanRead)
            {
                var form = await Request.ReadFormAsync();
                message = new OpenIdConnectMessage(form.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
            }

            if (message == null)
            {
                if (Options.SkipUnrecognizedRequests)
                {
                    // Not for us?
                    return AuthenticateResult.Skip();
                }
                return AuthenticateResult.Fail("No message.");
            }

            try
            {
                var messageReceivedContext = await RunMessageReceivedEventAsync(message);
                if (messageReceivedContext.HandledResponse)
                {
                    return AuthenticateResult.Success(messageReceivedContext.Ticket);
                }
                else if (messageReceivedContext.Skipped)
                {
                    return AuthenticateResult.Skip();
                }
                message = messageReceivedContext.ProtocolMessage;

                // Fail if state is missing, it's required for the correlation id.
                if (string.IsNullOrEmpty(message.State))
                {
                    // This wasn't a valid OIDC message, it may not have been intended for us.
                    if (Options.SkipUnrecognizedRequests)
                    {
                        return AuthenticateResult.Skip();
                    }
                    Logger.LogDebug(11, "message.State is null or empty.");
                    return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty);
                }

                // if state exists and we failed to 'unprotect' this is not a message we should process.
                var properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(message.State));
                if (properties == null)
                {
                    if (Options.SkipUnrecognizedRequests)
                    {
                        // Not for us?
                        return AuthenticateResult.Skip();
                    }
                    Logger.LogError(12, "Unable to read the message.State.");
                    return AuthenticateResult.Fail(Resources.MessageStateIsInvalid);
                }

                // if any of the error fields are set, throw error null
                if (!string.IsNullOrEmpty(message.Error))
                {
                    Logger.LogError(13, "Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.", message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null");
                    return AuthenticateResult.Fail(new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null")));
                }

                string userstate = null;
                properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate);
                message.State = userstate;

                if (!ValidateCorrelationId(properties))
                {
                    return AuthenticateResult.Fail("Correlation failed.");
                }

                if (_configuration == null && Options.ConfigurationManager != null)
                {
                    Logger.LogDebug(14, "Updating configuration");
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
                }

                Logger.LogTrace(15, "Authorization response received.");
                var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options, properties)
                {
                    ProtocolMessage = message
                };
                await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext);
                if (authorizationResponseReceivedContext.HandledResponse)
                {
                    Logger.LogDebug(16, "AuthorizationResponseReceived.HandledResponse");
                    return AuthenticateResult.Success(authorizationResponseReceivedContext.Ticket);
                }
                else if (authorizationResponseReceivedContext.Skipped)
                {
                    Logger.LogDebug(17, "AuthorizationResponseReceived.Skipped");
                    return AuthenticateResult.Skip();
                }
                message = authorizationResponseReceivedContext.ProtocolMessage;
                properties = authorizationResponseReceivedContext.Properties;

                if (string.IsNullOrEmpty(message.IdToken) && !string.IsNullOrEmpty(message.Code))
                {
                    return await HandleCodeOnlyFlow(message, properties);
                }
                else if (!string.IsNullOrEmpty(message.IdToken))
                {
                    return await HandleIdTokenFlows(message, properties);
                }
                else
                {
                    Logger.LogTrace(18, "Cannot process the message. Both id_token and code are missing.");
                    return AuthenticateResult.Fail(Resources.IdTokenCodeMissing);
                }
            }
            catch (Exception exception)
            {
                Logger.LogError(19, "Exception occurred while processing message.", exception);

                // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
                if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
                {
                    if (Options.ConfigurationManager != null)
                    {
                        Logger.LogDebug(20, "exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.");
                        Options.ConfigurationManager.RequestRefresh();
                    }
                }

                var authenticationFailedContext = await RunAuthenticationFailedEventAsync(message, exception);
                if (authenticationFailedContext.HandledResponse)
                {
                    return AuthenticateResult.Success(authenticationFailedContext.Ticket);
                }
                else if (authenticationFailedContext.Skipped)
                {
                    return AuthenticateResult.Skip();
                }

                throw;
            }
        }

        // Authorization Code Flow
        private async Task<AuthenticateResult> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
        {
            AuthenticationTicket ticket = null;
            JwtSecurityToken jwt = null;

            Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext()
            {
                ClientId = Options.ClientId,
                ProtocolMessage = message,
            });

            var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
            if (authorizationCodeReceivedContext.HandledResponse)
            {
                return AuthenticateResult.Success(authorizationCodeReceivedContext.Ticket);
            }
            else if (authorizationCodeReceivedContext.Skipped)
            {
                return AuthenticateResult.Skip();
            }
            message = authorizationCodeReceivedContext.ProtocolMessage;
            var code = authorizationCodeReceivedContext.Code;

            // Redeeming authorization code for tokens
            Logger.LogTrace(21, "Id Token is null. Redeeming code '{0}' for tokens.", code);

            var tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri);

            var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse, properties);
            if (authorizationCodeRedeemedContext.HandledResponse)
            {
                return AuthenticateResult.Success(authorizationCodeRedeemedContext.Ticket);
            }
            else if (authorizationCodeRedeemedContext.Skipped)
            {
                return AuthenticateResult.Skip();
            }

            message = authorizationCodeRedeemedContext.ProtocolMessage;
            tokenEndpointResponse = authorizationCodeRedeemedContext.TokenEndpointResponse;

            // no need to validate signature when token is received using "code flow" as per spec [http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation].
            var validationParameters = Options.TokenValidationParameters.Clone();
            validationParameters.ValidateSignature = false;

            ticket = ValidateToken(tokenEndpointResponse.IdToken, message, properties, validationParameters, out jwt);

            var nonce = jwt?.Payload.Nonce;
            if (!string.IsNullOrEmpty(nonce))
            {
                nonce = ReadNonceCookie(nonce);
            }

            Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
            {
                ClientId = Options.ClientId,
                ProtocolMessage = tokenEndpointResponse,
                ValidatedIdToken = jwt,
                Nonce = nonce
            });
<commit_msg>React to IdentityModel breaking change.<commit_after>// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;

namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{
    /// <summary>
    /// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware.
    /// </summary>
    public class OpenIdConnectHandler : RemoteAuthenticationHandler<OpenIdConnectOptions>
    {
        private const string NonceProperty = "N";
        private const string UriSchemeDelimiter = "://";

        private const string InputTagFormat = @"<input type=""hidden"" name=""{0}"" value=""{1}"" />";
        private const string HtmlFormFormat = @"<!doctype html>
<html>
<head>
    <title>Please wait while you're being redirected to the identity provider</title>
</head>
<body>
    <form name=""form"" method=""post"" action=""{0}"">
        {1}
        <noscript>Click here to finish the process: <input type=""submit"" /></noscript>
    </form>
    <script>document.form.submit();</script>
</body>
</html>";

        private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();

        private OpenIdConnectConfiguration _configuration;

        protected HttpClient Backchannel { get; private set; }

        protected HtmlEncoder HtmlEncoder { get; private set; }

        public OpenIdConnectHandler(HttpClient backchannel, HtmlEncoder htmlEncoder)
        {
            Backchannel = backchannel;
            HtmlEncoder = htmlEncoder;
        }

        /// <summary>
        /// Handles Signout
        /// </summary>
        /// <returns></returns>
        protected override async Task HandleSignOutAsync(SignOutContext signout)
        {
            if (signout != null)
            {
                if (_configuration == null && Options.ConfigurationManager != null)
                {
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
                }

                var message = new OpenIdConnectMessage()
                {
                    IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty),
                };

                // Set End_Session_Endpoint in order:
                // 1. properties.Redirect
                // 2. Options.PostLogoutRedirectUri
                var properties = new AuthenticationProperties(signout.Properties);
                var logoutRedirectUri = properties.RedirectUri;
                if (!string.IsNullOrEmpty(logoutRedirectUri))
                {
                    // Relative to PathBase
                    if (logoutRedirectUri.StartsWith("/", StringComparison.Ordinal))
                    {
                        logoutRedirectUri = BuildRedirectUri(logoutRedirectUri);
                    }
                    message.PostLogoutRedirectUri = logoutRedirectUri;
                }
                else if (!string.IsNullOrEmpty(Options.PostLogoutRedirectUri))
                {
                    logoutRedirectUri = Options.PostLogoutRedirectUri;
                    // Relative to PathBase
                    if (logoutRedirectUri.StartsWith("/", StringComparison.Ordinal))
                    {
                        logoutRedirectUri = BuildRedirectUri(logoutRedirectUri);
                    }
                    message.PostLogoutRedirectUri = logoutRedirectUri;
                }

                var principal = await Context.Authentication.AuthenticateAsync(Options.SignInScheme);
                message.IdTokenHint = principal?.FindFirst(OpenIdConnectParameterNames.IdToken)?.Value;

                var redirectContext = new RedirectContext(Context, Options, properties)
                {
                    ProtocolMessage = message
                };

                await Options.Events.RedirectToEndSessionEndpoint(redirectContext);
                if (redirectContext.HandledResponse)
                {
                    Logger.LogDebug(1, "RedirectToEndSessionEndpoint.HandledResponse");
                    return;
                }
                else if (redirectContext.Skipped)
                {
                    Logger.LogDebug(2, "RedirectToEndSessionEndpoint.Skipped");
                    return;
                }

                message = redirectContext.ProtocolMessage;

                if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet)
                {
                    var redirectUri = message.CreateLogoutRequestUrl();
                    if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
                    {
                        Logger.LogWarning(3, "The query string for Logout is not a well-formed URI. Redirect URI: '{0}'.", redirectUri);
                    }

                    Response.Redirect(redirectUri);
                }
                else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost)
                {
                    var inputs = new StringBuilder();
                    foreach (var parameter in message.Parameters)
                    {
                        var name = HtmlEncoder.Encode(parameter.Key);
                        var value = HtmlEncoder.Encode(parameter.Value);

                        var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value);
                        inputs.AppendLine(input);
                    }

                    var issuer = HtmlEncoder.Encode(message.IssuerAddress);

                    var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs);
                    var buffer = Encoding.UTF8.GetBytes(content);

                    Response.ContentLength = buffer.Length;
                    Response.ContentType = "text/html;charset=UTF-8";

                    // Emit Cache-Control=no-cache to prevent client caching.
                    Response.Headers[HeaderNames.CacheControl] = "no-cache";
                    Response.Headers[HeaderNames.Pragma] = "no-cache";
                    Response.Headers[HeaderNames.Expires] = "-1";

                    await Response.Body.WriteAsync(buffer, 0, buffer.Length);
                }
            }
        }

        /// <summary>
        /// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
        /// </summary>
        /// <returns></returns>
        /// <remarks>Uses log id's OIDCH-0026 - OIDCH-0050, next num: 37</remarks>
        protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            Logger.LogTrace(4, "Entering {0}." + nameof(HandleUnauthorizedAsync), GetType());

            // order for local RedirectUri
            // 1. challenge.Properties.RedirectUri
            // 2. CurrentUri if RedirectUri is not set)
            var properties = new AuthenticationProperties(context.Properties);

            if (string.IsNullOrEmpty(properties.RedirectUri))
            {
                properties.RedirectUri = CurrentUri;
            }
            Logger.LogTrace(5, "Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.", properties.RedirectUri);

            if (_configuration == null && Options.ConfigurationManager != null)
            {
                _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
            }

            var message = new OpenIdConnectMessage
            {
                ClientId = Options.ClientId,
                IssuerAddress = _configuration?.AuthorizationEndpoint ?? string.Empty,
                RedirectUri = BuildRedirectUri(Options.CallbackPath),
                Resource = Options.Resource,
                ResponseType = Options.ResponseType,
                Scope = string.Join(" ", Options.Scope)
            };

            // Omitting the response_mode parameter when it already corresponds to the default
            // response_mode used for the specified response_type is recommended by the specifications.
            // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
            if (!string.Equals(Options.ResponseType, OpenIdConnectResponseTypes.Code, StringComparison.Ordinal) ||
                !string.Equals(Options.ResponseMode, OpenIdConnectResponseModes.Query, StringComparison.Ordinal))
            {
                message.ResponseMode = Options.ResponseMode;
            }

            if (Options.ProtocolValidator.RequireNonce)
            {
                message.Nonce = Options.ProtocolValidator.GenerateNonce();
                WriteNonceCookie(message.Nonce);
            }

            GenerateCorrelationId(properties);

            var redirectContext = new RedirectContext(Context, Options, properties)
            {
                ProtocolMessage = message
            };

            await Options.Events.RedirectToAuthenticationEndpoint(redirectContext);
            if (redirectContext.HandledResponse)
            {
                Logger.LogDebug(6, "RedirectToAuthenticationEndpoint.HandledResponse");
                return true;
            }
            else if (redirectContext.Skipped)
            {
                Logger.LogDebug(7, "RedirectToAuthenticationEndpoint.Skipped");
                return false;
            }

            message = redirectContext.ProtocolMessage;

            if (!string.IsNullOrEmpty(message.State))
            {
                properties.Items[OpenIdConnectDefaults.UserstatePropertiesKey] = message.State;
            }

            // When redeeming a 'code' for an AccessToken, this value is needed
            properties.Items.Add(OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, message.RedirectUri);

            message.State = Options.StateDataFormat.Protect(properties);

            if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet)
            {
                var redirectUri = message.CreateAuthenticationRequestUrl();
                if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
                {
                    Logger.LogWarning(9, "The redirect URI is not well-formed. The URI is: '{0}'.", redirectUri);
                }

                Response.Redirect(redirectUri);
                return true;
            }
            else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost)
            {
                var inputs = new StringBuilder();
                foreach (var parameter in message.Parameters)
                {
                    var name = HtmlEncoder.Encode(parameter.Key);
                    var value = HtmlEncoder.Encode(parameter.Value);

                    var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value);
                    inputs.AppendLine(input);
                }

                var issuer = HtmlEncoder.Encode(message.IssuerAddress);

                var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs);
                var buffer = Encoding.UTF8.GetBytes(content);

                Response.ContentLength = buffer.Length;
                Response.ContentType = "text/html;charset=UTF-8";

                // Emit Cache-Control=no-cache to prevent client caching.
                Response.Headers[HeaderNames.CacheControl] = "no-cache";
                Response.Headers[HeaderNames.Pragma] = "no-cache";
                Response.Headers[HeaderNames.Expires] = "-1";

                await Response.Body.WriteAsync(buffer, 0, buffer.Length);
                return true;
            }

            throw new NotImplementedException($"An unsupported authentication method has been configured: {Options.AuthenticationMethod}");
        }

        /// <summary>
        /// Invoked to process incoming OpenIdConnect messages.
        /// </summary>
        /// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
        /// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks>
        protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
        {
            Logger.LogTrace(10, "Entering: {0}." + nameof(HandleRemoteAuthenticateAsync), GetType());

            OpenIdConnectMessage message = null;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                message = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));

                // response_mode=query (explicit or not) and a response_type containing id_token
                // or token are not considered as a safe combination and MUST be rejected.
                // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
                if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken))
                {
                    if (Options.SkipUnrecognizedRequests)
                    {
                        // Not for us?
                        return AuthenticateResult.Skip();
                    }
                    return AuthenticateResult.Fail("An OpenID Connect response cannot contain an " +
                            "identity token or an access token when using response_mode=query");
                }
            }
            // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
            else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
              && !string.IsNullOrEmpty(Request.ContentType)
              // May have media/type; charset=utf-8, allow partial match.
              && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
              && Request.Body.CanRead)
            {
                var form = await Request.ReadFormAsync();
                message = new OpenIdConnectMessage(form.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
            }

            if (message == null)
            {
                if (Options.SkipUnrecognizedRequests)
                {
                    // Not for us?
                    return AuthenticateResult.Skip();
                }
                return AuthenticateResult.Fail("No message.");
            }

            try
            {
                var messageReceivedContext = await RunMessageReceivedEventAsync(message);
                if (messageReceivedContext.HandledResponse)
                {
                    return AuthenticateResult.Success(messageReceivedContext.Ticket);
                }
                else if (messageReceivedContext.Skipped)
                {
                    return AuthenticateResult.Skip();
                }
                message = messageReceivedContext.ProtocolMessage;

                // Fail if state is missing, it's required for the correlation id.
                if (string.IsNullOrEmpty(message.State))
                {
                    // This wasn't a valid OIDC message, it may not have been intended for us.
                    if (Options.SkipUnrecognizedRequests)
                    {
                        return AuthenticateResult.Skip();
                    }
                    Logger.LogDebug(11, "message.State is null or empty.");
                    return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty);
                }

                // if state exists and we failed to 'unprotect' this is not a message we should process.
                var properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(message.State));
                if (properties == null)
                {
                    if (Options.SkipUnrecognizedRequests)
                    {
                        // Not for us?
                        return AuthenticateResult.Skip();
                    }
                    Logger.LogError(12, "Unable to read the message.State.");
                    return AuthenticateResult.Fail(Resources.MessageStateIsInvalid);
                }

                // if any of the error fields are set, throw error null
                if (!string.IsNullOrEmpty(message.Error))
                {
                    Logger.LogError(13, "Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.", message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null");
                    return AuthenticateResult.Fail(new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null")));
                }

                string userstate = null;
                properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate);
                message.State = userstate;

                if (!ValidateCorrelationId(properties))
                {
                    return AuthenticateResult.Fail("Correlation failed.");
                }

                if (_configuration == null && Options.ConfigurationManager != null)
                {
                    Logger.LogDebug(14, "Updating configuration");
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
                }

                Logger.LogTrace(15, "Authorization response received.");
                var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options, properties)
                {
                    ProtocolMessage = message
                };
                await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext);
                if (authorizationResponseReceivedContext.HandledResponse)
                {
                    Logger.LogDebug(16, "AuthorizationResponseReceived.HandledResponse");
                    return AuthenticateResult.Success(authorizationResponseReceivedContext.Ticket);
                }
                else if (authorizationResponseReceivedContext.Skipped)
                {
                    Logger.LogDebug(17, "AuthorizationResponseReceived.Skipped");
                    return AuthenticateResult.Skip();
                }
                message = authorizationResponseReceivedContext.ProtocolMessage;
                properties = authorizationResponseReceivedContext.Properties;

                if (string.IsNullOrEmpty(message.IdToken) && !string.IsNullOrEmpty(message.Code))
                {
                    return await HandleCodeOnlyFlow(message, properties);
                }
                else if (!string.IsNullOrEmpty(message.IdToken))
                {
                    return await HandleIdTokenFlows(message, properties);
                }
                else
                {
                    Logger.LogTrace(18, "Cannot process the message. Both id_token and code are missing.");
                    return AuthenticateResult.Fail(Resources.IdTokenCodeMissing);
                }
            }
            catch (Exception exception)
            {
                Logger.LogError(19, "Exception occurred while processing message.", exception);

                // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
                if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
                {
                    if (Options.ConfigurationManager != null)
                    {
                        Logger.LogDebug(20, "exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.");
                        Options.ConfigurationManager.RequestRefresh();
                    }
                }

                var authenticationFailedContext = await RunAuthenticationFailedEventAsync(message, exception);
                if (authenticationFailedContext.HandledResponse)
                {
                    return AuthenticateResult.Success(authenticationFailedContext.Ticket);
                }
                else if (authenticationFailedContext.Skipped)
                {
                    return AuthenticateResult.Skip();
                }

                throw;
            }
        }

        // Authorization Code Flow
        private async Task<AuthenticateResult> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
        {
            AuthenticationTicket ticket = null;
            JwtSecurityToken jwt = null;

            Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext()
            {
                ClientId = Options.ClientId,
                ProtocolMessage = message,
            });

            var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
            if (authorizationCodeReceivedContext.HandledResponse)
            {
                return AuthenticateResult.Success(authorizationCodeReceivedContext.Ticket);
            }
            else if (authorizationCodeReceivedContext.Skipped)
            {
                return AuthenticateResult.Skip();
            }
            message = authorizationCodeReceivedContext.ProtocolMessage;
            var code = authorizationCodeReceivedContext.Code;

            // Redeeming authorization code for tokens
            Logger.LogTrace(21, "Id Token is null. Redeeming code '{0}' for tokens.", code);

            var tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri);

            var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse, properties);
            if (authorizationCodeRedeemedContext.HandledResponse)
            {
                return AuthenticateResult.Success(authorizationCodeRedeemedContext.Ticket);
            }
            else if (authorizationCodeRedeemedContext.Skipped)
            {
                return AuthenticateResult.Skip();
            }

            message = authorizationCodeRedeemedContext.ProtocolMessage;
            tokenEndpointResponse = authorizationCodeRedeemedContext.TokenEndpointResponse;

            // no need to validate signature when token is received using "code flow" as per spec [http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation].
            var validationParameters = Options.TokenValidationParameters.Clone();
            validationParameters.RequireSignedTokens = false;

            ticket = ValidateToken(tokenEndpointResponse.IdToken, message, properties, validationParameters, out jwt);

            var nonce = jwt?.Payload.Nonce;
            if (!string.IsNullOrEmpty(nonce))
            {
                nonce = ReadNonceCookie(nonce);
            }

            Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
            {
                ClientId = Options.ClientId,
                ProtocolMessage = tokenEndpointResponse,
                ValidatedIdToken = jwt,
                Nonce = nonce
            });

{'fraction_non_alphanumeric': 0.08138063357282822, 'fraction_numerical': 0.0036332462442847813, 'mean_word_length': 1.8614063777596075, 'pattern_counts': {'":': 2, '<': 87, '<?xml version=': 0, '>': 91, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5566106', 'n_tokens_mistral': 11374, 'n_tokens_neox': 10674, 'n_words': 2378}