public async Task <LogoutRequest> CreateLogoutRequestAsync(IDictionary <string, string[]> requestParameters)
        {
            var(state, stateError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.State, _errorProvider);
            if (stateError != null)
            {
                return(LogoutRequest.Invalid(stateError));
            }

            var(logoutRedirectUri, redirectUriError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.PostLogoutRedirectUri, _errorProvider);
            if (redirectUriError != null)
            {
                return(LogoutRequest.Invalid(redirectUriError));
            }

            var(idTokenHint, idTokenHintError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.IdTokenHint, _errorProvider);
            if (idTokenHintError != null)
            {
                return(LogoutRequest.Invalid(idTokenHintError));
            }

            var redirectUriValidationResult = await _redirectUriValidator.ResolveLogoutUriAsync(null, logoutRedirectUri);

            if (!redirectUriValidationResult.IsValid)
            {
                return(LogoutRequest.Invalid(redirectUriValidationResult.Error));
            }

            return(LogoutRequest.Valid(new OpenIdConnectMessage(requestParameters), redirectUriValidationResult.Uri));
        }
        private (string responseType, string[] parsedResponseType, bool tokenRequested, OpenIdConnectMessage error) ValidateResponseType(IDictionary <string, string[]> parameters)
        {
            var(responseType, responseTypeParameterError) = RequestParametersHelper.ValidateParameterIsUnique(parameters, OpenIdConnectParameterNames.ResponseType, _errorProvider);
            var parsedResponseType = responseType?.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            var(tokenRequested, responseTypeValidationError) = parsedResponseType != null?IsValidResponseTypeCombination(parsedResponseType) : (false, null);

            return(responseType, parsedResponseType, tokenRequested, responseTypeParameterError ?? responseTypeValidationError);
        }
        private (string responseMode, OpenIdConnectMessage responseModeError) ValidateResponseMode(IDictionary <string, string[]> parameters)
        {
            var(responseMode, responseModeParameterError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(parameters, OpenIdConnectParameterNames.ResponseMode, _errorProvider);
            var responseModeValidationError = responseMode != null && !ValidResponseModes.Contains(responseMode) ?
                                              _errorProvider.InvalidParameterValue(responseMode, OpenIdConnectParameterNames.ResponseMode) :
                                              null;
            var isResponseModeInvalid = responseModeParameterError != null || responseModeValidationError != null;

            return(isResponseModeInvalid ? null : responseMode, responseModeParameterError ?? responseModeValidationError);
        }
        private async Task <(string clientId, string redirectUri, OpenIdConnectMessage error)> ValidateClientIdAndRedirectUri(
            IDictionary <string, string[]> requestParameters, string state)
        {
            var(clientId, clientIdError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, OpenIdConnectParameterNames.ClientId, _errorProvider);
            if (clientIdError != null)
            {
                clientIdError.State = state;
                return(null, null, clientIdError);
            }

            if (!await _clientIdValidator.ValidateClientIdAsync(clientId))
            {
                clientIdError       = _errorProvider.InvalidClientId(clientId);
                clientIdError.State = state;

                return(null, null, clientIdError);
            }

            var(redirectUri, redirectUriError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.RedirectUri, _errorProvider);
            if (redirectUriError != null)
            {
                redirectUriError.State = state;
                return(null, null, redirectUriError);
            }

            if (redirectUri != null)
            {
                if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
                {
                    redirectUriError       = _errorProvider.InvalidUriFormat(redirectUri);
                    redirectUriError.State = state;
                    return(null, null, redirectUriError);
                }

                var parsedUri = new Uri(redirectUri, UriKind.Absolute);
                if (!string.IsNullOrEmpty(parsedUri.Fragment))
                {
                    redirectUriError       = _errorProvider.InvalidUriFormat(redirectUri);
                    redirectUriError.State = state;
                    return(null, null, redirectUriError);
                }
            }

            var resolvedUriResult = await _redirectUrlValidator.ResolveRedirectUriAsync(clientId, redirectUri);

            if (!resolvedUriResult.IsValid)
            {
                resolvedUriResult.Error.State = state;
                return(null, null, resolvedUriResult.Error);
            }

            return(clientId, resolvedUriResult.Uri, null);
        }
        public async Task <TokenRequest> CreateTokenRequestAsync(IDictionary <string, string[]> requestParameters)
        {
            var(clientId, clientIdParameterError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, OpenIdConnectParameterNames.ClientId, _errorProvider);
            if (clientIdParameterError != null)
            {
                return(TokenRequest.Invalid(clientIdParameterError));
            }

            if (!await _clientIdValidator.ValidateClientIdAsync(clientId))
            {
                return(TokenRequest.Invalid(_errorProvider.InvalidClientId(clientId)));
            }

            var(clientSecret, clientSecretParameterError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.ClientSecret, _errorProvider);
            if (clientSecretParameterError != null)
            {
                return(TokenRequest.Invalid(clientSecretParameterError));
            }

            if (!await _clientIdValidator.ValidateClientCredentialsAsync(clientId, clientSecret))
            {
                return(TokenRequest.Invalid(_errorProvider.InvalidClientCredentials()));
            }

            var(grantType, grantTypeError) = RequestParametersHelper.ValidateParameterIsUnique(
                requestParameters,
                OpenIdConnectParameterNames.GrantType,
                _errorProvider);

            if (grantTypeError != null)
            {
                return(TokenRequest.Invalid(grantTypeError));
            }

            var grantTypeParameter = GetGrantTypeParameter(requestParameters, grantType);

            if (grantTypeParameter == null)
            {
                return(TokenRequest.Invalid(_errorProvider.InvalidGrantType(grantType)));
            }

            var(grantValue, grantValueError) = RequestParametersHelper.ValidateParameterIsUnique(
                requestParameters,
                grantTypeParameter,
                _errorProvider);

            if (grantValueError != null)
            {
                return(TokenRequest.Invalid(clientIdParameterError));
            }

            var message = new OpenIdConnectMessage(requestParameters)
            {
                RequestType = OpenIdConnectRequestType.Token
            };

            // TODO: File a bug to track we might want to redesign this if we want to consider other flows like
            // client credentials or resource owner credentials.
            var consentGrant = await _tokenManager.ExchangeTokenAsync(message);

            if (!consentGrant.IsValid)
            {
                return(TokenRequest.Invalid(consentGrant.Error));
            }

            if (!_timeStampManager.IsValidPeriod(consentGrant.Token.NotBefore, consentGrant.Token.Expires))
            {
                return(TokenRequest.Invalid(_errorProvider.InvalidLifetime()));
            }

            if (!string.Equals(clientId, consentGrant.ClientId, StringComparison.Ordinal))
            {
                return(TokenRequest.Invalid(_errorProvider.InvalidGrant()));
            }

            var(scope, requestScopesError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Scope, _errorProvider);
            if (requestScopesError != null)
            {
                return(TokenRequest.Invalid(requestScopesError));
            }

            var grantedScopes = consentGrant.GrantedScopes;

            var parsedScope = scope?.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            if (parsedScope != null)
            {
                var scopeResolutionResult = await _scopeResolver.ResolveScopesAsync(clientId, parsedScope);

                if (!scopeResolutionResult.IsValid)
                {
                    return(TokenRequest.Invalid(scopeResolutionResult.Error));
                }

                if (grantType.Equals(OpenIdConnectGrantTypes.AuthorizationCode, StringComparison.Ordinal) ||
                    grantType.Equals(OpenIdConnectGrantTypes.RefreshToken, StringComparison.Ordinal))
                {
                    if (scopeResolutionResult.Scopes.Any(rs => !consentGrant.GrantedScopes.Contains(rs)))
                    {
                        return(TokenRequest.Invalid(_errorProvider.UnauthorizedScope()));
                    }
                }

                grantedScopes = scopeResolutionResult.Scopes;
            }

            if (grantType.Equals(OpenIdConnectGrantTypes.AuthorizationCode, StringComparison.Ordinal))
            {
                var authorizationCodeError = await ValidateAuthorizationCode(
                    requestParameters,
                    clientId,
                    consentGrant);

                if (authorizationCodeError != null)
                {
                    return(TokenRequest.Invalid(authorizationCodeError));
                }
            }

            var requestGrants = new RequestGrants
            {
                Tokens = consentGrant.GrantedTokens.ToList(),
                Claims = consentGrant.Token.ToList(),
                Scopes = grantedScopes.ToList()
            };

            return(await ValidateRequestAsync(TokenRequest.Valid(
                                                  message,
                                                  consentGrant.UserId,
                                                  consentGrant.ClientId,
                                                  requestGrants)));
        }
        private async Task <OpenIdConnectMessage> ValidateAuthorizationCode(
            IDictionary <string, string[]> requestParameters,
            string clientId,
            AuthorizationGrant consentGrant)
        {
            if (!(consentGrant.Token is AuthorizationCode code))
            {
                throw new InvalidOperationException("Granted token must be an authorization code.");
            }

            var(redirectUri, redirectUriError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.RedirectUri, _errorProvider);
            if (redirectUriError != null)
            {
                return(redirectUriError);
            }

            var tokenRedirectUri = code.RedirectUri;

            if (redirectUri == null && tokenRedirectUri != null)
            {
                return(_errorProvider.MissingRequiredParameter(OpenIdConnectParameterNames.RedirectUri));
            }

            if (!string.Equals(redirectUri, tokenRedirectUri, StringComparison.Ordinal))
            {
                return(_errorProvider.MismatchedRedirectUrl(redirectUri));
            }

            var resolution = await _redirectUriValidator.ResolveRedirectUriAsync(clientId, redirectUri);

            if (!resolution.IsValid)
            {
                return(_errorProvider.InvalidRedirectUri(redirectUri));
            }

            if (code.CodeChallenge != null)
            {
                if (!ProofOfKeyForCodeExchangeChallengeMethods.SHA256.Equals(code.CodeChallengeMethod, StringComparison.Ordinal))
                {
                    throw new InvalidOperationException("Unsupported code challenge method.");
                }

                var(verifier, verifierError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, ProofOfKeyForCodeExchangeParameterNames.CodeVerifier, _errorProvider);
                if (verifierError != null)
                {
                    return(verifierError);
                }

                // code-verifier = [a-zA-Z0-9\-._~]{43,128}
                if (verifier.Length < 43 || verifier.Length > 128)
                {
                    return(_errorProvider.InvalidCodeVerifier());
                }

                for (var i = 0; i < verifier.Length; i++)
                {
                    if (verifier[i] > 127 || !ValidCodeVerifierCharacters[verifier[i]])
                    {
                        return(_errorProvider.InvalidCodeVerifier());
                    }
                }

                if (!string.Equals(code.CodeChallenge, GetComputedChallenge(verifier), StringComparison.Ordinal))
                {
                    return(_errorProvider.InvalidCodeVerifier());
                }
            }

            return(null);
        }
        public async Task <AuthorizationRequest> CreateAuthorizationRequestAsync(IDictionary <string, string[]> requestParameters)
        {
            // Parameters sent without a value MUST be treated as if they were
            // omitted from the request.The authorization server MUST ignore
            // unrecognized request parameters.Request and response parameters
            // MUST NOT be included more than once.

            // Validate that we only got send one state property as it needs to be included in all responses (including error ones)
            var(state, stateError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.State, _errorProvider);


            // Start by validating the client_id and redirect_uri as any of them being invalid indicates that we need to
            // return a 400 response instead of a 302 response with the error. This is signaled by the result not containing
            // a url to redirect to.
            var(clientId, redirectUri, clientError) = await ValidateClientIdAndRedirectUri(requestParameters, state);

            if (clientError != null)
            {
                // Send first the state error if there was one.
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                        stateError ?? clientError,
                                                        redirectUri: null,
                                                        responseMode: null)));
            }

            // We need to determine what response mode to use to send the errors in case there are any.
            // In case the response type and response modes are valid, we should use those values when
            // notifying clients of the errors.
            // In case there is an issue with the response type or the response mode we need to determine
            // how to notify the relying party of the errors.
            // We can divide this in two situations:
            // The response mode is invalid:
            //  * We ignore the response mode and base our response based on the response type specified.
            //      If a token was requested we send the error response on the fragment of the redirect uri.
            //      If no token was requested we send the error response on the query of the redirect uri.
            // The response type is invalid:
            //  * We try to determine if this is a hybrid or implicit flow:
            //      If the invalid response type contained a request for an id_token or an access_token, or
            //      contained more than one space separated value, we send the response on the fragment,
            //      unless the response mode is specified and form_post.
            //      If the invalid response type only contained one value and we can not determine is an
            //      implicit request flow, we return the error on the query string unless the response mode
            //      is specified and form_post or fragment.

            var(responseType, parsedResponseType, tokenRequested, responseTypeError) = ValidateResponseType(requestParameters);
            var(responseMode, responseModeError) = ValidateResponseMode(requestParameters);

            var invalidCombinationError = ValidateResponseModeTypeCombination(responseType, tokenRequested, responseMode);

            if (responseModeError != null || responseMode == null)
            {
                responseMode = GetResponseMode(parsedResponseType, tokenRequested);
            }

            if (responseTypeError != null)
            {
                responseTypeError.State = state;
                return(AuthorizationRequest.Invalid(
                           new AuthorizationRequestError(stateError ?? responseTypeError, redirectUri, responseMode)));
            }

            if (responseModeError != null)
            {
                responseModeError.State = state;
                return(AuthorizationRequest.Invalid(
                           new AuthorizationRequestError(stateError ?? responseModeError, redirectUri, responseMode)));
            }

            if (invalidCombinationError != null)
            {
                invalidCombinationError.State = state;
                return(AuthorizationRequest.Invalid(
                           new AuthorizationRequestError(stateError ?? invalidCombinationError, redirectUri, responseMode)));
            }

            var(nonce, nonceError) = tokenRequested ?
                                     RequestParametersHelper.ValidateParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Nonce, _errorProvider) :
                                     RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Nonce, _errorProvider);

            if (nonceError != null)
            {
                nonceError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(nonceError, redirectUri, responseMode)));
            }

            var(scope, scopeError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Scope, _errorProvider);
            if (scopeError != null)
            {
                scopeError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(scopeError, redirectUri, responseMode)));
            }

            var parsedScope   = scope.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            var allWhiteSpace = true;

            for (int i = 0; i < parsedScope.Length; i++)
            {
                allWhiteSpace = string.IsNullOrWhiteSpace(parsedScope[i]);
                if (!allWhiteSpace)
                {
                    break;
                }
            }

            if (allWhiteSpace)
            {
                scopeError       = _errorProvider.MissingRequiredParameter(OpenIdConnectParameterNames.Scope);
                scopeError.State = state;

                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                        scopeError,
                                                        redirectUri,
                                                        responseMode)));
            }

            if (parsedResponseType.Contains(OpenIdConnectResponseType.IdToken) && !parsedScope.Contains(OpenIdConnectScope.OpenId))
            {
                scopeError       = _errorProvider.MissingOpenIdScope();
                scopeError.State = state;

                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                        scopeError,
                                                        redirectUri,
                                                        responseMode)));
            }

            var resolvedScopes = await _scopeValidator.ResolveScopesAsync(clientId, parsedScope);

            if (!resolvedScopes.IsValid)
            {
                resolvedScopes.Error.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(resolvedScopes.Error, redirectUri, responseMode)));
            }

            var(prompt, promptError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Prompt, _errorProvider);
            if (promptError != null)
            {
                promptError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(promptError, redirectUri, responseMode)));
            }

            if (prompt != null)
            {
                var parsedPrompt = prompt.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                promptError = ValidatePrompt(parsedPrompt);
                if (promptError != null)
                {
                    promptError.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(promptError, redirectUri, responseMode)));
                }
            }

            var(codeChallenge, codeChallengeError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, ProofOfKeyForCodeExchangeParameterNames.CodeChallenge, _errorProvider);
            if (codeChallengeError != null)
            {
                codeChallengeError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(codeChallengeError, redirectUri, responseMode)));
            }

            if (codeChallenge != null)
            {
                // The code challenge needs to be 43 characters long as its the result of Base64URLEncode(SHA256(code_verifier)).
                // We do this check here because the code challenge might get saved in the serialized authorization code and we
                // want to prevent it from getting unnecessarily big.
                if (codeChallenge.Length != 43)
                {
                    var invalidCodeChallenge = _errorProvider.InvalidCodeChallenge();
                    invalidCodeChallenge.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                            invalidCodeChallenge,
                                                            redirectUri,
                                                            responseMode)));
                }

                var(codeChallengeMethod, codeChallengeMethodError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, ProofOfKeyForCodeExchangeParameterNames.CodeChallengeMethod, _errorProvider);
                if (codeChallengeMethodError != null)
                {
                    codeChallengeMethodError.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(codeChallengeMethodError, redirectUri, responseMode)));
                }

                if (!codeChallengeMethod.Equals(ProofOfKeyForCodeExchangeChallengeMethods.SHA256, StringComparison.Ordinal))
                {
                    var invalidChallengeMethod = _errorProvider.InvalidCodeChallengeMethod(codeChallengeMethod);
                    invalidChallengeMethod.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(invalidChallengeMethod, redirectUri, responseMode)));
                }
            }

            var result = new OpenIdConnectMessage(requestParameters);

            result.RequestType = OpenIdConnectRequestType.Authentication;

            var requestGrants = new RequestGrants
            {
                Tokens       = GetRequestedTokens(parsedResponseType, resolvedScopes.Scopes),
                Scopes       = resolvedScopes.Scopes.ToList(),
                ResponseMode = responseMode,
                RedirectUri  = redirectUri
            };

            return(await ValidateRequestAsync(AuthorizationRequest.Valid(result, requestGrants)));
        }