private async Task <TokenRequestValidationResult> ValidateCibaRequestRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of CIBA request"); ///////////////////////////////////////////// // check if client is authorized for grant type ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(OidcConstants.GrantTypes.Ciba)) { LogError("Client not authorized for CIBA flow"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } LicenseValidator.ValidateCiba(); ///////////////////////////////////////////// // validate authentication request id parameter ///////////////////////////////////////////// var authRequestId = parameters.Get(OidcConstants.TokenRequest.AuthenticationRequestId); if (authRequestId.IsMissing()) { LogError("Authentication request id is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } if (authRequestId.Length > _options.InputLengthRestrictions.AuthenticationRequestId) { LogError("Authentication request id too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate authentication request id ///////////////////////////////////////////// var validationContext = new BackchannelAuthenticationRequestIdValidationContext { AuthenticationRequestId = authRequestId, Request = _validatedRequest }; await _backchannelAuthenticationRequestIdValidator.ValidateAsync(validationContext); if (validationContext.Result.IsError) { return(validationContext.Result); } ////////////////////////////////////////////////////////// // resource indicator ////////////////////////////////////////////////////////// if (_validatedRequest.RequestedResourceIndicator != null && _validatedRequest.BackChannelAuthenticationRequest.RequestedResourceIndicators?.Any() == true && !_validatedRequest.BackChannelAuthenticationRequest.RequestedResourceIndicators.Contains(_validatedRequest.RequestedResourceIndicator)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator does not match any resource indicator in the original backchannel authentication request.")); } ////////////////////////////////////////////////////////// // resource and scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.BackChannelAuthenticationRequest.AuthorizedScopes, ResourceIndicators = _validatedRequest.BackChannelAuthenticationRequest.RequestedResourceIndicators, // if we are issuing a refresh token, then we need to allow the non-isolated resource IncludeNonIsolatedApiResources = _validatedRequest.BackChannelAuthenticationRequest.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess) }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); _logger.LogDebug("Validation of CIBA token request success"); return(Valid()); }
// todo: do we want to rework the semantics of these ignore params? // also seems like other workflows other than CC clients can omit scopes? private async Task <string> ValidateRequestedScopesAndResourcesAsync(NameValueCollection parameters, bool ignoreImplicitIdentityScopes = false, bool ignoreImplicitOfflineAccess = false) { var scopes = parameters.Get(OidcConstants.TokenRequest.Scope); if (scopes.IsMissing()) { _logger.LogTrace("Client provided no scopes - checking allowed scopes list"); if (!_validatedRequest.Client.AllowedScopes.IsNullOrEmpty()) { // this finds all the scopes the client is allowed to access var clientAllowedScopes = new List <string>(); if (!ignoreImplicitIdentityScopes) { var resources = await _resourceStore.FindResourcesByScopeAsync(_validatedRequest.Client.AllowedScopes); clientAllowedScopes.AddRange(resources.ToScopeNames().Where(x => _validatedRequest.Client.AllowedScopes.Contains(x))); } else { var apiScopes = await _resourceStore.FindApiScopesByNameAsync(_validatedRequest.Client.AllowedScopes); clientAllowedScopes.AddRange(apiScopes.Select(x => x.Name)); } if (!ignoreImplicitOfflineAccess) { if (_validatedRequest.Client.AllowOfflineAccess) { clientAllowedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess); } } scopes = clientAllowedScopes.Distinct().ToSpaceSeparatedString(); _logger.LogTrace("Defaulting to: {scopes}", scopes); } else { LogError("No allowed scopes configured for client", new { clientId = _validatedRequest.Client.ClientId }); return(OidcConstants.TokenErrors.InvalidScope); } } if (scopes.Length > _options.InputLengthRestrictions.Scope) { LogError("Scope parameter exceeds max allowed length"); return(OidcConstants.TokenErrors.InvalidScope); } var requestedScopes = scopes.ParseScopesString(); if (requestedScopes == null) { LogError("No scopes found in request"); return(OidcConstants.TokenErrors.InvalidScope); } var resourceIndicators = _validatedRequest.RequestedResourceIndicator == null? Enumerable.Empty <string>() : new[] { _validatedRequest.RequestedResourceIndicator }; var resourceValidationResult = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = requestedScopes, ResourceIndicators = resourceIndicators, // if the client is passing explicit scopes, we want to exclude the non-isolated resource scenaio from validation IncludeNonIsolatedApiResources = parameters.Get(OidcConstants.TokenRequest.Scope).IsMissing() }); if (!resourceValidationResult.Succeeded) { if (resourceValidationResult.InvalidResourceIndicators.Any()) { LogError("Invalid resource indicator"); return(OidcConstants.TokenErrors.InvalidTarget); } if (resourceValidationResult.InvalidScopes.Any()) { LogError("Invalid scopes requested"); } else { LogError("Invalid scopes for client requested"); } return(OidcConstants.TokenErrors.InvalidScope); } _validatedRequest.RequestedScopes = requestedScopes; LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = resourceValidationResult.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); return(null); }
private async Task <TokenRequestValidationResult> ValidateDeviceCodeRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of device code request"); ///////////////////////////////////////////// // resource indicator not supported for device flow ///////////////////////////////////////////// if (_validatedRequest.RequestedResourceIndicator != null) { LogError("Resource indicators not supported for device flow"); return(Invalid(OidcConstants.TokenErrors.InvalidTarget)); } ///////////////////////////////////////////// // check if client is authorized for grant type ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.DeviceFlow)) { LogError("Client not authorized for device flow"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } ///////////////////////////////////////////// // validate device code parameter ///////////////////////////////////////////// var deviceCode = parameters.Get(OidcConstants.TokenRequest.DeviceCode); if (deviceCode.IsMissing()) { LogError("Device code is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } if (deviceCode.Length > _options.InputLengthRestrictions.DeviceCode) { LogError("Device code too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate device code ///////////////////////////////////////////// var deviceCodeContext = new DeviceCodeValidationContext { DeviceCode = deviceCode, Request = _validatedRequest }; await _deviceCodeValidator.ValidateAsync(deviceCodeContext); if (deviceCodeContext.Result.IsError) { return(deviceCodeContext.Result); } ////////////////////////////////////////////////////////// // scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.DeviceCode.AuthorizedScopes, ResourceIndicators = null // not supported for device grant }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources; _logger.LogDebug("Validation of device code token request success"); return(Valid()); }
private async Task <TokenRequestValidationResult> ValidateRefreshTokenRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of refresh token request"); var refreshTokenHandle = parameters.Get(OidcConstants.TokenRequest.RefreshToken); if (refreshTokenHandle.IsMissing()) { LogError("Refresh token is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } if (refreshTokenHandle.Length > _options.InputLengthRestrictions.RefreshToken) { LogError("Refresh token too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } var result = await _refreshTokenService.ValidateRefreshTokenAsync(refreshTokenHandle, _validatedRequest.Client); if (result.IsError) { LogWarning("Refresh token validation failed. aborting"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _validatedRequest.RefreshToken = result.RefreshToken; _validatedRequest.RefreshTokenHandle = refreshTokenHandle; _validatedRequest.Subject = result.RefreshToken.Subject; ////////////////////////////////////////////////////////// // resource indicator ////////////////////////////////////////////////////////// var resourceIndicators = _validatedRequest.RefreshToken.AuthorizedResourceIndicators; if (_validatedRequest.RefreshToken.AuthorizedResourceIndicators != null) { // we had an authorization request so check current requested resource against original list if (_validatedRequest.RequestedResourceIndicator != null && !_validatedRequest.RefreshToken.AuthorizedResourceIndicators.Contains(_validatedRequest.RequestedResourceIndicator)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator does not match any resource indicator in the original authorize request.")); } } else if (!String.IsNullOrWhiteSpace(_validatedRequest.RequestedResourceIndicator)) { resourceIndicators = new[] { _validatedRequest.RequestedResourceIndicator }; } ////////////////////////////////////////////////////////// // resource and scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.RefreshToken.AuthorizedScopes, ResourceIndicators = resourceIndicators, // we're issuing refresh token, so we need to allow for non-isolated resource IncludeNonIsolatedApiResources = true, }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); _logger.LogDebug("Validation of refresh token request success"); // todo: more logging - similar to TokenValidator before return(Valid()); }
private async Task <TokenRequestValidationResult> ValidateAuthorizationCodeRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of authorization code token request"); ///////////////////////////////////////////// // check if client is authorized for grant type ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.AuthorizationCode) && !_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.Hybrid)) { LogError("Client not authorized for code flow"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } ///////////////////////////////////////////// // validate authorization code ///////////////////////////////////////////// var code = parameters.Get(OidcConstants.TokenRequest.Code); if (code.IsMissing()) { LogError("Authorization code is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } if (code.Length > _options.InputLengthRestrictions.AuthorizationCode) { LogError("Authorization code is too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _validatedRequest.AuthorizationCodeHandle = code; var authZcode = await _authorizationCodeStore.GetAuthorizationCodeAsync(code); if (authZcode == null) { LogError("Invalid authorization code", new { code }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate client binding ///////////////////////////////////////////// if (authZcode.ClientId != _validatedRequest.Client.ClientId) { LogError("Client is trying to use a code from a different client", new { clientId = _validatedRequest.Client.ClientId, codeClient = authZcode.ClientId }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } // remove code from store // todo: set to consumed in the future? await _authorizationCodeStore.RemoveAuthorizationCodeAsync(code); if (authZcode.CreationTime.HasExceeded(authZcode.Lifetime, _clock.UtcNow.UtcDateTime)) { LogError("Authorization code expired", new { code }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // populate session id ///////////////////////////////////////////// if (authZcode.SessionId.IsPresent()) { _validatedRequest.SessionId = authZcode.SessionId; } ///////////////////////////////////////////// // validate code expiration ///////////////////////////////////////////// if (authZcode.CreationTime.HasExceeded(_validatedRequest.Client.AuthorizationCodeLifetime, _clock.UtcNow.UtcDateTime)) { LogError("Authorization code is expired"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _validatedRequest.AuthorizationCode = authZcode; _validatedRequest.Subject = authZcode.Subject; ///////////////////////////////////////////// // validate redirect_uri ///////////////////////////////////////////// var redirectUri = parameters.Get(OidcConstants.TokenRequest.RedirectUri); if (redirectUri.IsMissing()) { LogError("Redirect URI is missing"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } if (redirectUri.Equals(_validatedRequest.AuthorizationCode.RedirectUri, StringComparison.Ordinal) == false) { LogError("Invalid redirect_uri", new { redirectUri, expectedRedirectUri = _validatedRequest.AuthorizationCode.RedirectUri }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate scopes are present ///////////////////////////////////////////// if (_validatedRequest.AuthorizationCode.RequestedScopes == null || !_validatedRequest.AuthorizationCode.RequestedScopes.Any()) { LogError("Authorization code has no associated scopes"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } ////////////////////////////////////////////////////////// // resource indicator ////////////////////////////////////////////////////////// if (_validatedRequest.RequestedResourceIndicator != null && _validatedRequest.AuthorizationCode.RequestedResourceIndicators?.Any() == true && !_validatedRequest.AuthorizationCode.RequestedResourceIndicators.Contains(_validatedRequest.RequestedResourceIndicator)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator does not match any resource indicator in the original authorize request.")); } ////////////////////////////////////////////////////////// // resource and scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.AuthorizationCode.RequestedScopes, ResourceIndicators = _validatedRequest.AuthorizationCode.RequestedResourceIndicators, // if we are issuing a refresh token, then we need to allow the non-isolated resource IncludeNonIsolatedApiResources = _validatedRequest.AuthorizationCode.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess) }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); ///////////////////////////////////////////// // validate PKCE parameters ///////////////////////////////////////////// var codeVerifier = parameters.Get(OidcConstants.TokenRequest.CodeVerifier); if (_validatedRequest.Client.RequirePkce || _validatedRequest.AuthorizationCode.CodeChallenge.IsPresent()) { _logger.LogDebug("Client required a proof key for code exchange. Starting PKCE validation"); var proofKeyResult = ValidateAuthorizationCodeWithProofKeyParameters(codeVerifier, _validatedRequest.AuthorizationCode); if (proofKeyResult.IsError) { return(proofKeyResult); } _validatedRequest.CodeVerifier = codeVerifier; } else { if (codeVerifier.IsPresent()) { LogError("Unexpected code_verifier: {codeVerifier}. This happens when the client is trying to use PKCE, but it is not enabled. Set RequirePkce to true.", codeVerifier); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } } ///////////////////////////////////////////// // make sure user is enabled ///////////////////////////////////////////// var isActiveCtx = new IsActiveContext(_validatedRequest.AuthorizationCode.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizationCodeValidation); await _profile.IsActiveAsync(isActiveCtx); if (isActiveCtx.IsActive == false) { LogError("User has been disabled", new { subjectId = _validatedRequest.AuthorizationCode.Subject.GetSubjectId() }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _logger.LogDebug("Validation of authorization code token request success"); return(Valid()); }
private async Task <AuthorizeRequestValidationResult> ValidateScopeAndResourceAsync(ValidatedAuthorizeRequest request) { ////////////////////////////////////////////////////////// // scope must be present ////////////////////////////////////////////////////////// var scope = request.Raw.Get(OidcConstants.AuthorizeRequest.Scope); if (scope.IsMissing()) { LogError("scope is missing", request); return(Invalid(request, description: "Invalid scope")); } if (scope.Length > _options.InputLengthRestrictions.Scope) { LogError("scopes too long.", request); return(Invalid(request, description: "Invalid scope")); } request.RequestedScopes = scope.FromSpaceSeparatedString().Distinct().ToList(); request.IsOpenIdRequest = request.RequestedScopes.Contains(IdentityServerConstants.StandardScopes.OpenId); ////////////////////////////////////////////////////////// // check scope vs response_type plausability ////////////////////////////////////////////////////////// var requirement = Constants.ResponseTypeToScopeRequirement[request.ResponseType]; if (requirement == Constants.ScopeRequirement.Identity || requirement == Constants.ScopeRequirement.IdentityOnly) { if (request.IsOpenIdRequest == false) { LogError("response_type requires the openid scope", request); return(Invalid(request, description: "Missing openid scope")); } } ////////////////////////////////////////////////////////// // check for resource indicators and valid format ////////////////////////////////////////////////////////// var resourceIndicators = request.Raw.GetValues(OidcConstants.TokenRequest.Resource) ?? Enumerable.Empty <string>(); if (resourceIndicators?.Any(x => x.Length > _options.InputLengthRestrictions.ResourceIndicatorMaxLength) == true) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator maximum length exceeded")); } if (!resourceIndicators.AreValidResourceIndicatorFormat(_logger)) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator format")); } // we don't want to allow resource indicators when "token" is requested to authorize endpoint if (request.GrantType == GrantType.Implicit && resourceIndicators.Any()) { // todo: correct error? return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicators not allowed for response_type 'token'.")); } request.RequestedResourceIndiators = resourceIndicators; ////////////////////////////////////////////////////////// // check if scopes are valid/supported and check for resource scopes ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = request.Client, Scopes = request.RequestedScopes, ResourceIndicators = resourceIndicators, IncludeNonIsolatedApiResources = request.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess), }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope")); } } LicenseValidator.ValidateResourceIndicators(resourceIndicators); if (validatedResources.Resources.IdentityResources.Any() && !request.IsOpenIdRequest) { LogError("Identity related scope requests, but no openid scope", request); return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Identity scopes requested, but openid scope is missing")); } if (validatedResources.Resources.ApiScopes.Any()) { request.IsApiResourceRequest = true; } ////////////////////////////////////////////////////////// // check id vs resource scopes and response types plausability ////////////////////////////////////////////////////////// var responseTypeValidationCheck = true; switch (requirement) { case Constants.ScopeRequirement.Identity: if (!validatedResources.Resources.IdentityResources.Any()) { _logger.LogError("Requests for id_token response type must include identity scopes"); responseTypeValidationCheck = false; } break; case Constants.ScopeRequirement.IdentityOnly: if (!validatedResources.Resources.IdentityResources.Any() || validatedResources.Resources.ApiScopes.Any()) { _logger.LogError("Requests for id_token response type only must not include resource scopes"); responseTypeValidationCheck = false; } break; case Constants.ScopeRequirement.ResourceOnly: if (validatedResources.Resources.IdentityResources.Any() || !validatedResources.Resources.ApiScopes.Any()) { _logger.LogError("Requests for token response type only must include resource scopes, but no identity scopes."); responseTypeValidationCheck = false; } break; } if (!responseTypeValidationCheck) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope for response type")); } request.ValidatedResources = validatedResources; return(Valid(request)); }
public async Task <BackchannelAuthenticationRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult) { using var activity = Tracing.BasicActivitySource.StartActivity("BackchannelAuthenticationRequestValidator.ValidateRequest"); if (clientValidationResult == null) { throw new ArgumentNullException(nameof(clientValidationResult)); } _logger.LogDebug("Start backchannel authentication request validation"); _validatedRequest = new ValidatedBackchannelAuthenticationRequest { Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)), Options = _options }; _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation); ////////////////////////////////////////////////////////// // Client must be configured for CIBA ////////////////////////////////////////////////////////// if (!clientValidationResult.Client.AllowedGrantTypes.Contains(OidcConstants.GrantTypes.Ciba)) { LogError("Client {clientId} not configured with the CIBA grant type.", clientValidationResult.Client.ClientId); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnauthorizedClient, "Unauthorized client")); } LicenseValidator.ValidateCiba(); ////////////////////////////////////////////////////////// // load request object ////////////////////////////////////////////////////////// var jwtRequest = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.Request); // check length restrictions if (jwtRequest.IsPresent()) { if (jwtRequest.Length >= _options.InputLengthRestrictions.Jwt) { LogError("request value is too long"); return(Invalid(OidcConstants.AuthorizeErrors.InvalidRequestObject, "Invalid request value")); } } _validatedRequest.RequestObject = jwtRequest; ////////////////////////////////////////////////////////// // validate request object ////////////////////////////////////////////////////////// var roValidationResult = await TryValidateRequestObjectAsync(); if (!roValidationResult.Success) { return(roValidationResult.ErrorResult); } if (_validatedRequest.Client.RequireRequestObject && !_validatedRequest.RequestObjectValues.Any()) { LogError("Client is configured for RequireRequestObject but none present"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest)); } ////////////////////////////////////////////////////////// // scope must be present ////////////////////////////////////////////////////////// var scope = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.Scope); if (scope.IsMissing()) { LogError("Missing scope"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Missing scope")); } if (scope.Length > _options.InputLengthRestrictions.Scope) { LogError("scopes too long."); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid scope")); } _validatedRequest.RequestedScopes = scope.FromSpaceSeparatedString().Distinct().ToList(); ////////////////////////////////////////////////////////// // openid scope required ////////////////////////////////////////////////////////// if (!_validatedRequest.RequestedScopes.Contains(IdentityServerConstants.StandardScopes.OpenId)) { LogError("openid scope missing."); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Missing the openid scope")); } ////////////////////////////////////////////////////////// // check for resource indicators and valid format ////////////////////////////////////////////////////////// var resourceIndicators = _validatedRequest.Raw.GetValues("resource") ?? Enumerable.Empty <string>(); if (resourceIndicators?.Any(x => x.Length > _options.InputLengthRestrictions.ResourceIndicatorMaxLength) == true) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidTarget, "Resource indicator maximum length exceeded")); } if (!resourceIndicators.AreValidResourceIndicatorFormat(_logger)) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidTarget, "Invalid resource indicator format")); } _validatedRequest.RequestedResourceIndiators = resourceIndicators?.ToList(); ////////////////////////////////////////////////////////// // check if scopes are valid/supported and check for resource scopes ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.RequestedScopes, ResourceIndicators = resourceIndicators, IncludeNonIsolatedApiResources = _validatedRequest.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess), }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidTarget, "Invalid resource indicator")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidScope, "Invalid scope")); } } LicenseValidator.ValidateResourceIndicators(resourceIndicators); _validatedRequest.ValidatedResources = validatedResources; ////////////////////////////////////////////////////////// // check requested_expiry ////////////////////////////////////////////////////////// var requestLifetime = _validatedRequest.Client.CibaLifetime ?? _options.Ciba.DefaultLifetime; var requestedExpiry = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.RequestedExpiry); if (requestedExpiry.IsPresent()) { // Int32.MaxValue == 2147483647, which is 10 characters in length // so using 9 so we don't overflow below on the Int32.Parse if (requestedExpiry.Length > 9) { LogError("requested_expiry too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid requested_expiry")); } if (Int32.TryParse(requestedExpiry, out var expiryValue) && expiryValue > 0 && expiryValue <= requestLifetime) { _validatedRequest.Expiry = expiryValue; } else { LogError("requested_expiry value out of valid range"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid requested_expiry")); } } else { _validatedRequest.Expiry = requestLifetime; } ////////////////////////////////////////////////////////// // check acr_values ////////////////////////////////////////////////////////// var acrValues = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.AcrValues); if (acrValues.IsPresent()) { if (acrValues.Length > _options.InputLengthRestrictions.AcrValues) { LogError("Acr values too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid acr_values")); } _validatedRequest.AuthenticationContextReferenceClasses = acrValues.FromSpaceSeparatedString().Distinct().ToList(); ////////////////////////////////////////////////////////// // check custom acr_values: idp and tenant ////////////////////////////////////////////////////////// var tenant = _validatedRequest.AuthenticationContextReferenceClasses.FirstOrDefault(x => x.StartsWith(KnownAcrValues.Tenant)); if (tenant != null) { _validatedRequest.AuthenticationContextReferenceClasses.Remove(tenant); tenant = tenant.Substring(KnownAcrValues.Tenant.Length); _validatedRequest.Tenant = tenant; } var idp = _validatedRequest.AuthenticationContextReferenceClasses.FirstOrDefault(x => x.StartsWith(KnownAcrValues.HomeRealm)); if (idp != null) { _validatedRequest.AuthenticationContextReferenceClasses.Remove(idp); idp = idp.Substring(KnownAcrValues.HomeRealm.Length); // check if idp is present but client does not allow it, and then ignore it if (_validatedRequest.Client.IdentityProviderRestrictions != null && _validatedRequest.Client.IdentityProviderRestrictions.Any()) { if (!_validatedRequest.Client.IdentityProviderRestrictions.Contains(idp)) { _logger.LogWarning("idp requested ({idp}) is not in client restriction list.", idp); idp = null; } } _validatedRequest.IdP = idp; } } ////////////////////////////////////////////////////////// // login hints ////////////////////////////////////////////////////////// var loginHint = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.LoginHint); var loginHintToken = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.LoginHintToken); var idTokenHint = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.IdTokenHint); var loginHintCount = 0; if (loginHint.IsPresent()) { loginHintCount++; } if (loginHintToken.IsPresent()) { loginHintCount++; } if (idTokenHint.IsPresent()) { loginHintCount++; } if (loginHintCount == 0) { LogError("Missing login_hint_token, id_token_hint, or login_hint"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Missing login_hint_token, id_token_hint, or login_hint")); } else if (loginHintCount > 1) { LogError("Too many of login_hint_token, id_token_hint, or login_hint"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Too many of login_hint_token, id_token_hint, or login_hint")); } ////////////////////////////////////////////////////////// // check login_hint ////////////////////////////////////////////////////////// if (loginHint.IsPresent()) { if (loginHint.Length > _options.InputLengthRestrictions.LoginHint) { LogError("Login hint too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid login_hint")); } _validatedRequest.LoginHint = loginHint; } ////////////////////////////////////////////////////////// // check login_hint_token ////////////////////////////////////////////////////////// if (loginHintToken.IsPresent()) { if (loginHintToken.Length > _options.InputLengthRestrictions.LoginHintToken) { LogError("Login hint token too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid login_hint_token")); } _validatedRequest.LoginHintToken = loginHintToken; } ////////////////////////////////////////////////////////// // check id_token_hint ////////////////////////////////////////////////////////// if (idTokenHint.IsPresent()) { if (idTokenHint.Length > _options.InputLengthRestrictions.IdTokenHint) { LogError("id token hint too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid id_token_hint")); } var idTokenHintValidationResult = await _tokenValidator.ValidateIdentityTokenAsync(idTokenHint, _validatedRequest.ClientId, false); if (idTokenHintValidationResult.IsError) { LogError("id token hint failed to validate: " + idTokenHintValidationResult.Error, idTokenHintValidationResult.ErrorDescription); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid id_token_hint")); } _validatedRequest.IdTokenHint = idTokenHint; _validatedRequest.IdTokenHintClaims = idTokenHintValidationResult.Claims; } ////////////////////////////////////////////////////////// // check user_code ////////////////////////////////////////////////////////// var userCode = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.UserCode); if (userCode.IsPresent()) { if (userCode.Length > _options.InputLengthRestrictions.UserCode) { LogError("user_code too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid user_code")); } _validatedRequest.UserCode = userCode; } ////////////////////////////////////////////////////////// // check binding_message ////////////////////////////////////////////////////////// var bindingMessage = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.BindingMessage); if (bindingMessage.IsPresent()) { if (bindingMessage.Length > _options.InputLengthRestrictions.BindingMessage) { LogError("binding_message too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidBindingMessage, "Invalid binding_message")); } _validatedRequest.BindingMessage = bindingMessage; } ////////////////////////////////////////////////////////// // validate the login hint w/ custom validator ////////////////////////////////////////////////////////// var userResult = await _backchannelAuthenticationUserValidator.ValidateRequestAsync(new BackchannelAuthenticationUserValidatorContext { Client = _validatedRequest.Client, IdTokenHint = _validatedRequest.IdTokenHint, LoginHint = _validatedRequest.LoginHint, LoginHintToken = _validatedRequest.LoginHintToken, IdTokenHintClaims = _validatedRequest.IdTokenHintClaims, UserCode = _validatedRequest.UserCode, BindingMessage = _validatedRequest.BindingMessage }); if (userResult.IsError) { if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.AccessDenied) { LogError("Request was denied access for that user"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.AccessDenied, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.ExpiredLoginHintToken) { LogError("Expired login_hint_token"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.ExpiredLoginHintToken, userResult.ErrorDescription ?? "Expired login_hint_token")); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId) { LogError("Unknown user id"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.MissingUserCode) { LogError("Missing user_code"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.MissingUserCode, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.InvalidUserCode) { LogError("Invalid user_code"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidUserCode, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.InvalidBindingMessage) { LogError("Invalid binding_message"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidBindingMessage, userResult.ErrorDescription)); } LogError("Unexpected error from IBackchannelAuthenticationUserValidator: {error}", userResult.Error); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId)); } if (userResult.Subject == null || !userResult.Subject.HasClaim(x => x.Type == JwtClaimTypes.Subject)) { LogError("No subject or subject id returned from IBackchannelAuthenticationUserValidator"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId)); } _validatedRequest.Subject = userResult.Subject; LogSuccess(); return(new BackchannelAuthenticationRequestValidationResult(_validatedRequest)); }