public async Task <IActionResult> Exchange([ModelBinder(BinderType = typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request) { if (request.IsClientCredentialsGrantType()) { // Note: the client credentials are automatically validated by OpenIddict: // if client_id or client_secret are invalid, this action won't be invoked. var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } // Create a new authentication ticket. var ticket = CreateTicket(application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsClientCredentialsGrantType()) { // Note: the client credentials are automatically validated by OpenIddict: // if client_id or client_secret are invalid, this action won't be invoked. var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } // Create a new authentication ticket. var ticket = CreateTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public async Task <IActionResult> Token(OpenIdConnectRequest request) { // Warning: this action is decorated with IgnoreAntiforgeryTokenAttribute to override // the global antiforgery token validation policy applied by the MVC modules stack, // which is required for this stateless OAuth2/OIDC token endpoint to work correctly. // To prevent effective CSRF/session fixation attacks, this action MUST NOT return // an authentication cookie or try to establish an ASP.NET Core user session. if (request.IsPasswordGrantType()) { return(await ExchangePasswordGrantType(request)); } if (request.IsClientCredentialsGrantType()) { return(await ExchangeClientCredentialsGrantType(request)); } if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { return(await ExchangeAuthorizationCodeOrRefreshTokenGrantType(request)); } throw new NotSupportedException("The specified grant type is not supported."); }
public async Task <IActionResult> Exchange([ModelBinder(typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request) { if (!request.IsClientCredentialsGrantType()) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); } var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } var ticket = CreateTicket(application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { try { _logger.LogInformation("About to authorize user"); if (request.IsClientCredentialsGrantType()) { var authenticationTicket = await CreateTicket(request); //this occur when the client credentials are wrong if (authenticationTicket == null) { var properties = new AuthenticationProperties(new Dictionary <string, string> { [Properties.Error] = Errors.InvalidGrant, [Properties.ErrorDescription] = "The client id/secret couple is invalid." }); return(Forbid(properties, OpenIddictServerDefaults.AuthenticationScheme)); } _logger.LogInformation("Client Credentials authentication successfully."); return(SignIn(authenticationTicket.Principal, authenticationTicket.Properties, authenticationTicket.AuthenticationScheme)); } return(BadRequest("Invalid grant type")); } catch (Exception ex) { _logger.LogError(ex, ex.Message); return(BadRequest(ex.Message)); } }
public async Task <IActionResult> Exchange([ModelBinder(typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request) { try { if (request.IsClientCredentialsGrantType()) { // Note: the client credentials are automatically validated by OpenIddict: // if client_id or client_secret are invalid, this action will not be invoked var ticket = await _mediator.Send(new ClientCredentialsRequest { ClientId = request.ClientId }, HttpContext.RequestAborted); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } } catch (IdentityException ex) { return(BadRequest(ex.Response)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public async Task <IActionResult> Login(OpenIdConnectRequest request, Profile profile) { if (request.IsClientCredentialsGrantType()) { var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "Id Клиента не найден в базе данных." })); } if (!await _repository.GetProfileAsync(profile.Login, profile.Password)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "Login or PAssword не совпадают." })); } var idprofile = await _repository.GetProfileIdAsync(profile.Login, profile.Password); var i = idprofile.ToString(); request.Username = i.ToString(); var ticket = CreateTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsRefreshTokenGrantType()) { var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "Id Клиента не найден в базе данных." })); } var ticket = CreateTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public void IsClientCredentialsGrantType_ReturnsExpectedResult(string type, bool result) { // Arrange var request = new OpenIdConnectRequest(); request.GrantType = type; // Act and assert Assert.Equal(result, request.IsClientCredentialsGrantType()); }
private async Task <IActionResult> HandleAuthorizationFlow(OpenIdConnectRequest request) { if (request.IsClientCredentialsGrantType()) { return(await HandleClientCredentialsGrantTypeFlow(request)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public async Task <ActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true); if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); var claimsPrincipal = await _userClaimsPrincipalFactory.CreateAsync(user); var limitedPermissions = _authorizationOptions.LimitedCookiePermissions?.Split(PlatformConstants.Security.Claims.PermissionClaimTypeDelimiter, StringSplitOptions.RemoveEmptyEntries) ?? new string[0]; if (!user.IsAdministrator) { limitedPermissions = claimsPrincipal .Claims .Where(c => c.Type == PlatformConstants.Security.Claims.PermissionClaimType) .Select(c => c.Value) .Intersect(limitedPermissions, StringComparer.OrdinalIgnoreCase) .ToArray(); } if (limitedPermissions.Any()) { // Set limited permissions and authenticate user with combined mode Cookies + Bearer. // // LimitedPermissions claims that will be granted to the user by cookies when bearer token authentication is enabled. // This can help to authorize the user for direct(non - AJAX) GET requests to the VC platform API and / or to use some 3rd - party web applications for the VC platform(like Hangfire dashboard). // // If the user identity has claim named "limited_permissions", this attribute should authorize only permissions listed in that claim. Any permissions that are required by this attribute but // not listed in the claim should cause this method to return false. However, if permission limits of user identity are not defined ("limited_permissions" claim is missing), // then no limitations should be applied to the permissions. ((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim(PlatformConstants.Security.Claims.LimitedPermissionsClaimType, string.Join(PlatformConstants.Security.Claims.PermissionClaimTypeDelimiter, limitedPermissions))); await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, claimsPrincipal); } return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the authorization code/refresh token. var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsClientCredentialsGrantType()) { // Note: the client credentials are automatically validated by OpenIddict: // if client_id or client_secret are invalid, this action won't be invoked. var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } // Create a new authentication ticket. var ticket = CreateTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
private async Task <bool> InvokeTokenEndpointAsync() { if (!string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogError("The token request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed token request has been received: make sure to use POST." })); } // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Options.Logger.LogError("The token request was rejected because the " + "mandatory 'Content-Type' header was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed token request has been received: " + "the mandatory 'Content-Type' header was missing from the POST request." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogError("The token request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed token request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } var request = new OpenIdConnectRequest(await Request.ReadFormAsync()); // Note: set the message type before invoking the ExtractTokenRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.Token); // Store the token request in the OWIN context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractTokenRequestContext(Context, Options, request); await Options.Provider.ExtractTokenRequest(@event); if (@event.HandledResponse) { return(true); } else if (@event.Skipped) { return(false); } else if (@event.IsRejected) { Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } // Reject token requests missing the mandatory grant_type parameter. if (string.IsNullOrEmpty(request.GrantType)) { Options.Logger.LogError("The token request was rejected because the grant type was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'grant_type' parameter was missing.", })); } // Reject grant_type=authorization_code requests if the authorization endpoint is disabled. else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue) { Options.Logger.LogError("The token request was rejected because the authorization code grant was disabled."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The authorization code grant is not allowed by this authorization server." })); } // Reject grant_type=authorization_code requests missing the authorization code. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code)) { Options.Logger.LogError("The token request was rejected because the authorization code was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'code' parameter was missing." })); } // Reject grant_type=refresh_token requests missing the refresh token. // See https://tools.ietf.org/html/rfc6749#section-6 else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken)) { Options.Logger.LogError("The token request was rejected because the refresh token was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'refresh_token' parameter was missing." })); } // Reject grant_type=password requests missing username or password. // See https://tools.ietf.org/html/rfc6749#section-4.3.2 else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))) { Options.Logger.LogError("The token request was rejected because the resource owner credentials were missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'username' and/or 'password' parameters " + "was/were missing from the request message." })); } // When client_id and client_secret are both null, try to extract them from the Authorization header. // See http://tools.ietf.org/html/rfc6749#section-2.3.1 and // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication if (string.IsNullOrEmpty(request.ClientId) && string.IsNullOrEmpty(request.ClientSecret)) { var header = Request.Headers.Get("Authorization"); if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) { try { var value = header.Substring("Basic ".Length).Trim(); var data = Encoding.UTF8.GetString(Convert.FromBase64String(value)); var index = data.IndexOf(':'); if (index >= 0) { request.ClientId = data.Substring(0, index); request.ClientSecret = data.Substring(index + 1); } } catch (FormatException) { } catch (ArgumentException) { } } } var context = new ValidateTokenRequestContext(Context, Options, request); await Options.Provider.ValidateTokenRequest(context); // If the validation context was set as fully validated, // mark the OpenID Connect request as confidential. if (context.IsValidated) { request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, OpenIdConnectConstants.ConfidentialityLevels.Private); } if (context.HandledResponse) { return(true); } else if (context.Skipped) { return(false); } else if (context.IsRejected) { Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Reject grant_type=client_credentials requests if validation was skipped. else if (context.IsSkipped && request.IsClientCredentialsGrantType()) { Options.Logger.LogError("The token request must be fully validated to use the client_credentials grant type."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required when using client_credentials." })); } // Ensure that the client_id has been set from the ValidateTokenRequest event. else if (context.IsValidated && string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The token request was validated but the client_id was not set."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal server error occurred." })); } // At this stage, client_id cannot be null for grant_type=authorization_code requests, // as it must either be set in the ValidateTokenRequest notification // by the developer or manually flowed by non-confidential client applications. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "client_id was missing from the token request" })); } AuthenticationTicket ticket = null; // See http://tools.ietf.org/html/rfc6749#section-4.1 // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant). // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant). if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { ticket = request.IsAuthorizationCodeGrantType() ? await DeserializeAuthorizationCodeAsync(request.Code, request) : await DeserializeRefreshTokenAsync(request.RefreshToken, request); if (ticket == null) { Options.Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid ticket" })); } // If the client was fully authenticated when retrieving its refresh token, // the current request must be rejected if client authentication was not enforced. if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential()) { Options.Logger.LogError("The token request was rejected because client authentication " + "was required to use the confidential refresh token."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required to use this ticket" })); } if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Options.Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was expired."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Expired ticket" })); } // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token // was issued to a public client but cannot be null for an authorization code grant request. var presenters = ticket.GetPresenters(); if (request.IsAuthorizationCodeGrantType() && !presenters.Any()) { Options.Logger.LogError("The token request was rejected because the authorization " + "code didn't contain any valid presenter."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal server error occurred." })); } // Ensure the authorization code/refresh token was issued to the client application making the token request. // Note: when using the refresh token grant, client_id is optional but must validated if present. // As a consequence, this check doesn't depend on the actual status of client authentication. // See https://tools.ietf.org/html/rfc6749#section-6 // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken if (!string.IsNullOrEmpty(request.ClientId) && presenters.Any() && !presenters.Contains(request.ClientId, StringComparer.Ordinal)) { Options.Logger.LogError("The token request was rejected because the authorization " + "code was issued to a different client application."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Ticket does not contain matching client_id" })); } // Validate the redirect_uri flowed by the client application during this token request. // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri // but the specifications allow proceeding the token request without returning an error // if the authorization request didn't contain an explicit redirect_uri. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation var address = ticket.GetProperty(OpenIdConnectConstants.Properties.RedirectUri); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address)) { ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, null); if (string.IsNullOrEmpty(request.RedirectUri)) { Options.Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "redirect_uri was missing from the token request" })); } else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal)) { Options.Logger.LogError("The token request was rejected because the 'redirect_uri' " + "parameter didn't correspond to the expected value."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Authorization code does not contain matching redirect_uri" })); } } // If a code challenge was initially sent in the authorization request and associated with the // code, validate the code verifier to ensure the token request is sent by a legit caller. var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge)) { ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, null); // Get the code verifier from the token request. // If it cannot be found, return an invalid_grant error. var verifier = request.CodeVerifier; if (string.IsNullOrEmpty(verifier)) { Options.Logger.LogError("The token request was rejected because the required 'code_verifier' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The required 'code_verifier' was missing from the token request." })); } // Note: the code challenge method is always validated when receiving the authorization request. var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod); ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, null); Debug.Assert(string.IsNullOrEmpty(method) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal), "The specified code challenge method should be supported."); // If the S256 challenge method was used, compute the hash corresponding to the code verifier. if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal)) { using (var algorithm = SHA256.Create()) { // Compute the SHA-256 hash of the code verifier and encode it using base64-url. // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information. var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier)); verifier = Base64UrlEncoder.Encode(hash); } } // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge)) { Options.Logger.LogError("The token request was rejected because the 'code_verifier' was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'code_verifier' was invalid." })); } } if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Resource)) { // When an explicit resource parameter has been included in the token request // but was missing from the initial request, the request MUST be rejected. var resources = ticket.GetResources(); if (!resources.Any()) { Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not allowed."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request cannot contain a resource parameter " + "if the authorization request didn't contain one" })); } // When an explicit resource parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain resources // that were not allowed during the initial authorization/token request. else if (!new HashSet <string>(resources).IsSupersetOf(request.GetResources())) { Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not valid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request doesn't contain a valid resource parameter" })); } } if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope)) { // When an explicit scope parameter has been included in the token request // but was missing from the initial request, the request MUST be rejected. // See http://tools.ietf.org/html/rfc6749#section-6 var scopes = ticket.GetScopes(); if (!scopes.Any()) { Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request cannot contain a scope parameter " + "if the authorization request didn't contain one" })); } // When an explicit scope parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain scopes // that were not allowed during the initial authorization/token request. // See https://tools.ietf.org/html/rfc6749#section-6 else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes())) { Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not valid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request doesn't contain a valid scope parameter" })); } } } var notification = new HandleTokenRequestContext(Context, Options, request, ticket); await Options.Provider.HandleTokenRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } else if (notification.IsRejected) { Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, /* Description: */ notification.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // Flow the changes made to the ticket. ticket = notification.Ticket; // Ensure an authentication ticket has been provided or return // an error code indicating that the grant type is not supported. if (ticket == null) { Options.Logger.LogError("The token request was rejected because no authentication " + "ticket was returned by application code."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant_type parameter is not supported." })); } return(await HandleSignInAsync(ticket)); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true); if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the authorization code/refresh token. var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsClientCredentialsGrantType()) { // Note: the client credentials are automatically validated by OpenIddict: // if client_id or client_secret are invalid, this action won't be invoked. var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } // Create a new authentication ticket. var ticket = CreateTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
private async Task <bool> InvokeTokenEndpointAsync() { if (!HttpMethods.IsPost(Request.Method)) { Logger.LogError("The token request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Logger.LogError("The token request was rejected because the " + "mandatory 'Content-Type' header was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'Content-Type' header must be specified." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The token request was rejected because an invalid 'Content-Type' " + "header was specified: {ContentType}.", Request.ContentType); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Content-Type' header is not valid." })); } var request = new OpenIdConnectRequest(await Request.ReadFormAsync(Context.RequestAborted)); // Note: set the message type before invoking the ExtractTokenRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.TokenRequest); // Store the token request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractTokenRequestContext(Context, Scheme, Options, request); await Provider.ExtractTokenRequest(@event); if (@event.Result != null) { if (@event.Result.Handled) { Logger.LogDebug("The token request was handled in user code."); return(true); } else if (@event.Result.Skipped) { Logger.LogDebug("The default token request handling was skipped from user code."); return(false); } } else if (@event.IsRejected) { Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The token request was successfully extracted " + "from the HTTP request: {Request}.", request); // Reject token requests missing the mandatory grant_type parameter. if (string.IsNullOrEmpty(request.GrantType)) { Logger.LogError("The token request was rejected because the grant type was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'grant_type' parameter is missing.", })); } // Reject grant_type=authorization_code requests if the authorization endpoint is disabled. else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue) { Logger.LogError("The token request was rejected because the authorization code grant was disabled."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The authorization code grant is not allowed by this authorization server." })); } // Reject grant_type=authorization_code requests missing the authorization code. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code)) { Logger.LogError("The token request was rejected because the authorization code was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'code' parameter is missing." })); } // Reject grant_type=refresh_token requests missing the refresh token. // See https://tools.ietf.org/html/rfc6749#section-6 else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken)) { Logger.LogError("The token request was rejected because the refresh token was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'refresh_token' parameter is missing." })); } // Reject grant_type=password requests missing username or password. // See https://tools.ietf.org/html/rfc6749#section-4.3.2 else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))) { Logger.LogError("The token request was rejected because the resource owner credentials were missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'username' and/or 'password' parameters are missing." })); } // Try to resolve the client credentials specified in the 'Authorization' header. // If they cannot be extracted, fallback to the client_id/client_secret parameters. var credentials = Request.Headers.GetClientCredentials(); if (credentials != null) { // Reject requests that use multiple client authentication methods. // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. if (!string.IsNullOrEmpty(request.ClientSecret)) { Logger.LogError("The token request was rejected because multiple client credentials were specified."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Multiple client credentials cannot be specified." })); } request.ClientId = credentials?.Key; request.ClientSecret = credentials?.Value; } var context = new ValidateTokenRequestContext(Context, Scheme, Options, request); await Provider.ValidateTokenRequest(context); // If the validation context was set as fully validated, // mark the OpenID Connect request as confidential. if (context.IsValidated) { request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, OpenIdConnectConstants.ConfidentialityLevels.Private); } if (context.Result != null) { if (context.Result.Handled) { Logger.LogDebug("The token request was handled in user code."); return(true); } else if (context.Result.Skipped) { Logger.LogDebug("The default token request handling was skipped from user code."); return(false); } } else if (context.IsRejected) { Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Reject grant_type=client_credentials requests if validation was skipped. else if (context.IsSkipped && request.IsClientCredentialsGrantType()) { Logger.LogError("The token request must be fully validated to use the client_credentials grant type."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required when using the client credentials grant." })); } // At this stage, client_id cannot be null for grant_type=authorization_code requests, // as it must either be set in the ValidateTokenRequest notification // by the developer or manually flowed by non-confidential client applications. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.ClientId)) { Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'client_id' parameter is missing." })); } // Store the validated client_id as a request property. request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId); Logger.LogInformation("The token request was successfully validated."); AuthenticationTicket ticket = null; // See http://tools.ietf.org/html/rfc6749#section-4.1 // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant). // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant). if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { ticket = request.IsAuthorizationCodeGrantType() ? await DeserializeAuthorizationCodeAsync(request.Code, request) : await DeserializeRefreshTokenAsync(request.RefreshToken, request); if (ticket == null) { Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = request.IsAuthorizationCodeGrantType() ? "The specified authorization code is invalid." : "The specified refresh token is invalid." })); } // If the client was fully authenticated when retrieving its refresh token, // the current request must be rejected if client authentication was not enforced. if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential()) { Logger.LogError("The token request was rejected because client authentication " + "was required to use the confidential refresh token."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required to use the specified refresh token." })); } if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was expired."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = request.IsAuthorizationCodeGrantType() ? "The specified authorization code is no longer valid." : "The specified refresh token is no longer valid." })); } // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token // was issued to a public client but cannot be null for an authorization code grant request. var presenters = ticket.GetPresenters(); if (request.IsAuthorizationCodeGrantType() && !presenters.Any()) { throw new InvalidOperationException("The presenters list cannot be extracted from the authorization code."); } // Ensure the authorization code/refresh token was issued to the client application making the token request. // Note: when using the refresh token grant, client_id is optional but must validated if present. // As a consequence, this check doesn't depend on the actual status of client authentication. // See https://tools.ietf.org/html/rfc6749#section-6 // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken //if (!string.IsNullOrEmpty(context.ClientId) && presenters.Any() && // !presenters.Contains(context.ClientId, StringComparer.Ordinal)) //{ // Logger.LogError("The token request was rejected because the authorization " + // "code was issued to a different client application."); // return await SendTokenResponseAsync(new OpenIdConnectResponse // { // Error = OpenIdConnectConstants.Errors.InvalidGrant, // ErrorDescription = request.IsAuthorizationCodeGrantType() ? // "The specified authorization code cannot be used by this client application." : // "The specified refresh token cannot be used by this client application." // }); //} // Validate the redirect_uri flowed by the client application during this token request. // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri // but the specifications allow proceeding the token request without returning an error // if the authorization request didn't contain an explicit redirect_uri. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation var address = ticket.GetProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address)) { if (string.IsNullOrEmpty(request.RedirectUri)) { Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'redirect_uri' parameter is missing." })); } else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal)) { Logger.LogError("The token request was rejected because the 'redirect_uri' " + "parameter didn't correspond to the expected value."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'redirect_uri' parameter doesn't match the client " + "redirection endpoint the authorization code was initially sent to." })); } } // If a code challenge was initially sent in the authorization request and associated with the // code, validate the code verifier to ensure the token request is sent by a legit caller. var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge)) { // Get the code verifier from the token request. // If it cannot be found, return an invalid_grant error. var verifier = request.CodeVerifier; if (string.IsNullOrEmpty(verifier)) { Logger.LogError("The token request was rejected because the required 'code_verifier' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'code_verifier' parameter is missing." })); } // Note: the code challenge method is always validated when receiving the authorization request. var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod); Debug.Assert(string.IsNullOrEmpty(method) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal), "The specified code challenge method should be supported."); // If the S256 challenge method was used, compute the hash corresponding to the code verifier. if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal)) { using (var algorithm = SHA256.Create()) { // Compute the SHA-256 hash of the code verifier and encode it using base64-url. // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information. var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier)); verifier = Base64UrlEncoder.Encode(hash); } } // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge)) { Logger.LogError("The token request was rejected because the 'code_verifier' was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'code_verifier' parameter is invalid." })); } } if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope)) { // When an explicit scope parameter has been included in the token request // but was missing from the initial request, the request MUST be rejected. // See http://tools.ietf.org/html/rfc6749#section-6 var scopes = ticket.GetScopes(); if (!scopes.Any()) { Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The 'scope' parameter is not valid in this context." })); } // When an explicit scope parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain scopes // that were not allowed during the initial authorization/token request. // See https://tools.ietf.org/html/rfc6749#section-6 else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes())) { Logger.LogError("The token request was rejected because the 'scope' parameter was not valid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'scope' parameter is invalid." })); } } } var notification = new HandleTokenRequestContext(Context, Scheme, Options, request, ticket); await Provider.HandleTokenRequest(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The token request was handled in user code."); return(true); } else if (notification.Result.Skipped) { Logger.LogDebug("The default token request handling was skipped from user code."); return(false); } } else if (notification.IsRejected) { Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, /* Description: */ notification.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // Flow the changes made to the ticket. ticket = notification.Ticket; // Ensure an authentication ticket has been provided or return // an error code indicating that the request was rejected. if (ticket == null) { Logger.LogError("The token request was rejected because it was not handled by the user code."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The token request was rejected by the authorization server." })); } return(await SignInAsync(ticket)); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByEmailAsync(request.Username) ?? await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Please check that your email and password is correct" })); } // Ensure the user is enabled. if (!user.IsEnabled) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user account is disabled" })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, true); // Ensure the user is not already locked out. if (result.IsLockedOut) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user account has been suspended" })); } // Reject the token request if two-factor authentication has been enabled by the user. if (result.RequiresTwoFactor) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid login procedure" })); } // Ensure the user is allowed to sign in. if (result.IsNotAllowed) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in" })); } if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Please check that your email and password is correct" })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the refresh token. var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the refresh token. // Note: if you want to automatically invalidate the refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The refresh token is no longer valid" })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in" })); } // Create a new authentication ticket, but reuse the properties stored // in the refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsClientCredentialsGrantType()) { var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } var ticket = CreateWorkplaceSignInTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported" })); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "用户名或密码错误" })); } if (user.IsDeleted) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "用户名或密码错误" })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true); if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "用户名或密码错误" })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); await _userLoginLogManager.CreateAsync(new UserLoginLog { LoginTime = DateTime.Now, TrueName = user.TrueName, UserId = user.Id, UserName = user.UserName, OrganizationId = user.OrganizationId, LoginApplication = request.ClientId, LoginIp = HttpContext.Connection.RemoteIpAddress.ToString(), }, CancellationToken.None); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the authorization code/refresh token. var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } if (user.IsDeleted) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); await _userLoginLogManager.CreateAsync(new UserLoginLog { LoginTime = DateTime.Now, TrueName = user.TrueName, UserId = user.Id, UserName = user.UserName, OrganizationId = user.OrganizationId, LoginApplication = request.ClientId, LoginIp = HttpContext.Connection.RemoteIpAddress.ToString(), }, CancellationToken.None); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.GrantType == "openid") { var oid = request["openid"]; if (oid.HasValue) { string openId = oid.Value.Value.ToString(); string hasOpenid = _cache.GetString(openId); if (hasOpenid == "1") { _cache.Remove(oid.Value.Value.ToString()); } else { return(BadRequest(new OpenIdConnectResponse { Error = "illegal_request", ErrorDescription = "非法请求" })); } var user = _dbContext.Users.Where(x => x.WXOpenId == openId).FirstOrDefault(); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = "login_error", ErrorDescription = "用户不存在" })); } if (user.IsDeleted) { return(BadRequest(new OpenIdConnectResponse { Error = "login_error", ErrorDescription = "用户不存在" })); } var ticket = await CreateTicketAsync(request, user); await _userLoginLogManager.CreateAsync(new UserLoginLog { LoginTime = DateTime.Now, TrueName = user.TrueName, UserId = user.Id, UserName = user.UserName, OrganizationId = user.OrganizationId, LoginApplication = request.ClientId, LoginIp = HttpContext.Connection.RemoteIpAddress.ToString(), }, CancellationToken.None); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } } else if (request.IsClientCredentialsGrantType()) { var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } // Create a new authentication ticket. var ticket = CreateApplicationTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.GrantType == "face") { var faceImage = request["image"]; var username = request["username"]; if (faceImage.HasValue && username.HasValue) { string image = faceImage.Value.Value.ToString(); string uid = username.Value.Value.ToString(); BDFaceVerifyRequest faceRequest = new BDFaceVerifyRequest(); faceRequest.uid = uid; faceRequest.topNum = 1; faceRequest.image = image; var r = await restClient.Post <ResponseMessage <BDFaceVerifyResponse> >("/baidu/face/verify", faceRequest); if (r.IsSuccess() && r.Extension != null && r.Extension.result != null && r.Extension.result[0] >= 80) { var user = _dbContext.Users.Where(x => x.UserName.ToLower() == uid.ToLower()).FirstOrDefault(); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = "login_error", ErrorDescription = "用户不存在" })); } if (user.IsDeleted) { return(BadRequest(new OpenIdConnectResponse { Error = "login_error", ErrorDescription = "用户不存在" })); } var ticket = await CreateTicketAsync(request, user); await _userLoginLogManager.CreateAsync(new UserLoginLog { LoginTime = DateTime.Now, TrueName = user.TrueName, UserId = user.Id, UserName = user.UserName, OrganizationId = user.OrganizationId, LoginApplication = request.ClientId, LoginIp = HttpContext.Connection.RemoteIpAddress.ToString() }, CancellationToken.None); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "人脸认证失败" })); } } } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }