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