private OpenIdConnectProtocolException CreateOpenIdConnectProtocolException(OpenIdConnectMessage message)
        {
            var description = message.ErrorDescription ?? "error_description is null";
            var errorUri    = message.ErrorUri ?? "error_uri is null";

            var errorMessage = string.Format(
                CultureInfo.InvariantCulture,
                Resources.Exception_OpenIdConnectMessageError,
                message.Error,
                description,
                errorUri);

            _logger.WriteError(errorMessage);

            var ex = new OpenIdConnectProtocolException(errorMessage);

            ex.Data["error"]             = message.Error;
            ex.Data["error_description"] = description;
            ex.Data["error_uri"]         = errorUri;
            return(ex);
        }
        /// <summary>
        /// Invoked to process incoming authentication messages.
        /// </summary>
        /// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
        protected override async Task <AuthenticationTicket> AuthenticateCoreAsync()
        {
            // Allow login to be constrained to a specific path. Need to make this runtime configurable.
            if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
            {
                return(null);
            }

            OpenIdConnectMessage authorizationResponse = null;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase) && Request.Query.Any())
            {
                authorizationResponse = 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(authorizationResponse.IdToken) || !string.IsNullOrEmpty(authorizationResponse.AccessToken))
                {
                    var invalidResponseEx = new OpenIdConnectProtocolException("An OpenID Connect response cannot contain an identity token or an access token when using response_mode=query");

                    _logger.WriteError("Exception occurred while processing message: ", invalidResponseEx);

                    var authenticationFailedNotification = new AuthenticationFailedNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
                    {
                        ProtocolMessage = authorizationResponse,
                        Exception       = invalidResponseEx
                    };
                    await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);

                    if (authenticationFailedNotification.HandledResponse)
                    {
                        return(GetHandledResponseTicket());
                    }
                    if (authenticationFailedNotification.Skipped)
                    {
                        return(null);
                    }

                    throw invalidResponseEx;
                }
            }
            // 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.IsNullOrWhiteSpace(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)
            {
                if (!Request.Body.CanSeek)
                {
                    _logger.WriteVerbose("Buffering request body");
                    // Buffer in case this body was not meant for us.
                    MemoryStream memoryStream = new MemoryStream();
                    await Request.Body.CopyToAsync(memoryStream);

                    memoryStream.Seek(0, SeekOrigin.Begin);
                    Request.Body = memoryStream;
                }

                IFormCollection form = await Request.ReadFormAsync();

                Request.Body.Seek(0, SeekOrigin.Begin);

                // TODO: a delegate on OpenIdConnectAuthenticationOptions would allow for users to hook their own custom message.
                authorizationResponse = new OpenIdConnectMessage(form);
            }

            if (authorizationResponse == null)
            {
                return(null);
            }

            ExceptionDispatchInfo authFailedEx = null;

            try
            {
                var messageReceivedNotification = new MessageReceivedNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
                {
                    ProtocolMessage = authorizationResponse
                };
                await Options.Notifications.MessageReceived(messageReceivedNotification);

                if (messageReceivedNotification.HandledResponse)
                {
                    return(GetHandledResponseTicket());
                }
                if (messageReceivedNotification.Skipped)
                {
                    return(null);
                }

                // runtime always adds state, if we don't find it OR we failed to 'unprotect' it this is not a message we
                // should process.
                AuthenticationProperties properties = GetPropertiesFromState(authorizationResponse.State);
                if (properties == null)
                {
                    _logger.WriteWarning("The state field is missing or invalid.");
                    return(null);
                }

                // devs will need to hook AuthenticationFailedNotification to avoid having 'raw' runtime errors displayed to users.
                if (!string.IsNullOrWhiteSpace(authorizationResponse.Error))
                {
                    throw CreateOpenIdConnectProtocolException(authorizationResponse);
                }

                if (_configuration == null)
                {
                    _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.Request.CallCancelled);
                }

                PopulateSessionProperties(authorizationResponse, properties);

                ClaimsPrincipal      user   = null;
                AuthenticationTicket ticket = null;
                JwtSecurityToken     jwt    = null;
                string nonce = null;
                // Copy and augment to avoid cross request race conditions for updated configurations.
                var validationParameters = Options.TokenValidationParameters.Clone();

                // Hybrid or Implicit flow
                if (!string.IsNullOrEmpty(authorizationResponse.IdToken))
                {
                    var securityTokenReceivedNotification = new SecurityTokenReceivedNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
                    {
                        ProtocolMessage = authorizationResponse,
                    };
                    await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);

                    if (securityTokenReceivedNotification.HandledResponse)
                    {
                        return(GetHandledResponseTicket());
                    }
                    if (securityTokenReceivedNotification.Skipped)
                    {
                        return(null);
                    }

                    user = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt);

                    if (Options.ProtocolValidator.RequireNonce)
                    {
                        if (string.IsNullOrWhiteSpace(authorizationResponse.Nonce))
                        {
                            authorizationResponse.Nonce = jwt.Payload.Nonce;
                        }

                        // deletes the nonce cookie
                        nonce = RetrieveNonce(authorizationResponse);
                    }

                    ClaimsIdentity claimsIdentity = user.Identity as ClaimsIdentity;
                    ticket = new AuthenticationTicket(claimsIdentity, properties);

                    var securityTokenValidatedNotification = new SecurityTokenValidatedNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
                    {
                        AuthenticationTicket = ticket,
                        ProtocolMessage      = authorizationResponse,
                    };
                    await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);

                    if (securityTokenValidatedNotification.HandledResponse)
                    {
                        return(GetHandledResponseTicket());
                    }
                    if (securityTokenValidatedNotification.Skipped)
                    {
                        return(null);
                    }
                    // Flow possible changes
                    ticket = securityTokenValidatedNotification.AuthenticationTicket;
                }

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

                OpenIdConnectMessage tokenEndpointResponse = null;

                // Authorization Code or Hybrid flow
                if (!string.IsNullOrEmpty(authorizationResponse.Code))
                {
                    var tokenEndpointRequest = new OpenIdConnectMessage()
                    {
                        ClientId     = Options.ClientId,
                        ClientSecret = Options.ClientSecret,
                        Code         = authorizationResponse.Code,
                        GrantType    = OpenIdConnectGrantTypes.AuthorizationCode,
                        RedirectUri  = properties.Dictionary.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey) ?
                                       properties.Dictionary[OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey] : string.Empty,
                    };

                    var authorizationCodeReceivedNotification = new AuthorizationCodeReceivedNotification(Context, Options)
                    {
                        AuthenticationTicket = ticket,
                        Code                 = authorizationResponse.Code,
                        JwtSecurityToken     = jwt,
                        ProtocolMessage      = authorizationResponse,
                        RedirectUri          = tokenEndpointRequest.RedirectUri,
                        TokenEndpointRequest = tokenEndpointRequest
                    };

                    // PKCE https://tools.ietf.org/html/rfc7636#section-4.5
                    string codeVerifier;
                    if (properties.Dictionary.TryGetValue(OAuthConstants.CodeVerifierKey, out codeVerifier))
                    {
                        tokenEndpointRequest.Parameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier);
                        properties.Dictionary.Remove(OAuthConstants.CodeVerifierKey);
                    }

                    await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);

                    if (authorizationCodeReceivedNotification.HandledResponse)
                    {
                        return(GetHandledResponseTicket());
                    }
                    if (authorizationCodeReceivedNotification.Skipped)
                    {
                        return(null);
                    }
                    // Flow possible changes
                    authorizationResponse = authorizationCodeReceivedNotification.ProtocolMessage;
                    ticket = authorizationCodeReceivedNotification.AuthenticationTicket;
                    tokenEndpointRequest  = authorizationCodeReceivedNotification.TokenEndpointRequest;
                    tokenEndpointResponse = authorizationCodeReceivedNotification.TokenEndpointResponse;
                    jwt = authorizationCodeReceivedNotification.JwtSecurityToken;

                    if (!authorizationCodeReceivedNotification.HandledCodeRedemption && Options.RedeemCode)
                    {
                        tokenEndpointResponse = await RedeemAuthorizationCodeAsync(tokenEndpointRequest);
                    }

                    if (tokenEndpointResponse != null)
                    {
                        var tokenResponseReceivedNotification = new TokenResponseReceivedNotification(Context, Options)
                        {
                            ProtocolMessage       = authorizationResponse,
                            TokenEndpointResponse = tokenEndpointResponse
                        };
                        await Options.Notifications.TokenResponseReceived(tokenResponseReceivedNotification);

                        if (tokenResponseReceivedNotification.HandledResponse)
                        {
                            return(GetHandledResponseTicket());
                        }
                        if (tokenResponseReceivedNotification.Skipped)
                        {
                            return(null);
                        }

                        // 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].
                        validationParameters.RequireSignedTokens = false;

                        // At least a cursory validation is required on the new IdToken, even if we've already validated the one from the authorization response.
                        // And we'll want to validate the new JWT in ValidateTokenResponse.
                        JwtSecurityToken tokenEndpointJwt = null;
                        var tokenEndpointUser             = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out tokenEndpointJwt);

                        // Avoid running the event, etc, if it was already done as part of the authorization response validation.
                        if (user == null)
                        {
                            if (Options.ProtocolValidator.RequireNonce)
                            {
                                if (string.IsNullOrWhiteSpace(tokenEndpointResponse.Nonce))
                                {
                                    tokenEndpointResponse.Nonce = tokenEndpointJwt.Payload.Nonce;
                                }

                                // deletes the nonce cookie
                                if (nonce == null)
                                {
                                    nonce = RetrieveNonce(tokenEndpointResponse);
                                }
                            }

                            ClaimsIdentity claimsIdentity = tokenEndpointUser.Identity as ClaimsIdentity;
                            ticket = new AuthenticationTicket(claimsIdentity, properties);

                            var securityTokenValidatedNotification = new SecurityTokenValidatedNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
                            {
                                AuthenticationTicket = ticket,
                                ProtocolMessage      = tokenEndpointResponse,
                            };
                            await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);

                            if (securityTokenValidatedNotification.HandledResponse)
                            {
                                return(GetHandledResponseTicket());
                            }
                            if (securityTokenValidatedNotification.Skipped)
                            {
                                return(null);
                            }
                            // Flow possible changes
                            ticket = securityTokenValidatedNotification.AuthenticationTicket;
                        }
                        else
                        {
                            if (!string.Equals(jwt.Subject, tokenEndpointJwt.Subject, StringComparison.Ordinal))
                            {
                                throw new SecurityTokenException("The sub claim does not match in the id_token's from the authorization and token endpoints.");
                            }
                        }

                        jwt = tokenEndpointJwt;
                    }

                    // Validate the token response if it wasn't provided manually
                    if (!authorizationCodeReceivedNotification.HandledCodeRedemption && Options.RedeemCode)
                    {
                        Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
                        {
                            ClientId         = Options.ClientId,
                            ProtocolMessage  = tokenEndpointResponse,
                            ValidatedIdToken = jwt,
                            Nonce            = nonce
                        });
                    }
                }

                if (Options.SaveTokens && ticket != null)
                {
                    SaveTokens(ticket.Properties, tokenEndpointResponse ?? authorizationResponse);
                }

                return(ticket);
            }
            catch (Exception exception)
            {
                // We can't await inside a catch block, capture and handle outside.
                authFailedEx = ExceptionDispatchInfo.Capture(exception);
            }

            if (authFailedEx != null)
            {
                _logger.WriteError("Exception occurred while processing message: ", authFailedEx.SourceException);

                // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
                if (Options.RefreshOnIssuerKeyNotFound && authFailedEx.SourceException.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
                {
                    Options.ConfigurationManager.RequestRefresh();
                }

                var authenticationFailedNotification = new AuthenticationFailedNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
                {
                    ProtocolMessage = authorizationResponse,
                    Exception       = authFailedEx.SourceException
                };
                await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);

                if (authenticationFailedNotification.HandledResponse)
                {
                    return(GetHandledResponseTicket());
                }
                if (authenticationFailedNotification.Skipped)
                {
                    return(null);
                }

                authFailedEx.Throw();
            }

            return(null);
        }