private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { // Note: in this sample, the granted scopes match the requested scope // but you may want to allow the user to uncheck specific scopes. // For that, simply restrict the list of scopes before calling SetScopes. ticket.SetScopes(request.GetScopes()); ticket.SetResources("resource_server"); } foreach (var claim in ticket.Principal.Claims) { claim.SetDestinations(GetDestinations(claim, ticket)); } return(ticket); }
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."); }
private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest oidcRequest, ApplicationUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (!oidcRequest.IsAuthorizationCodeGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(oidcRequest.GetScopes())); } ticket.SetResources("resource_server"); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
public void IsAuthorizationCodeGrantType_ReturnsExpectedResult(string type, bool result) { // Arrange var request = new OpenIdConnectRequest(); request.GrantType = type; // Act and assert Assert.Equal(result, request.IsAuthorizationCodeGrantType()); }
/// <summary> /// Creates the ticket asynchronous. /// </summary> /// <param name="request">The request.</param> /// <param name="properties">The properties.</param> /// <returns></returns> private async Task <AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, object usr, AuthenticationProperties properties = null) { var user = usr as User; var principal = await signInManager.CreateUserPrincipalAsync(user); var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { var resource = new List <string>(); configuration.Bind("ApiResources", resource); ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Phone, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, Scopes.Roles, "location", }.Intersect(request.GetScopes())); var clientList = new List <string>(); configuration.Bind("ApiResources", clientList); ticket.SetResources(clientList); } var identity = principal.Identity as ClaimsIdentity; if (ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) { if (!string.IsNullOrWhiteSpace(user.Email)) { identity.AddClaim("email", user.Email, OpenIdConnectConstants.Destinations.IdentityToken); } } if (ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) { if (!string.IsNullOrWhiteSpace(user.PhoneNumber)) { identity.AddClaim("phone", user.PhoneNumber, OpenIdConnectConstants.Destinations.IdentityToken); } } foreach (var claim in ticket.Principal.Claims) { claim.SetDestinations(GetDestinations(claim, ticket)); } return(ticket); }
public static async Task <AuthenticationTicket> CreateAuthenticationTicket( OpenIddictApplicationManager <BTCPayOpenIdClient> applicationManager, OpenIddictAuthorizationManager <BTCPayOpenIdAuthorization> authorizationManager, IdentityOptions identityOptions, SignInManager <ApplicationUser> signInManager, OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { ticket.SetScopes(request.GetScopes()); } else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(ticket.GetInternalAuthorizationId())) { var app = await applicationManager.FindByClientIdAsync(request.ClientId); var authorizationId = await IsUserAuthorized(authorizationManager, request, user.Id, app.Id); if (!string.IsNullOrEmpty(authorizationId)) { ticket.SetInternalAuthorizationId(authorizationId); } } foreach (var claim in ticket.Principal.Claims) { claim.SetDestinations(GetDestinations(identityOptions, claim, ticket)); } return(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.IsAuthorizationCodeGrantType()) { // Retrieve the claims principal stored in the authorization code. var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); //You may be interested in this sample code from OpenIddict samples on how to do this with ASP.NET Core Authentication. //In this case however we just pass through the token information that was generated in the Accept action. // Retrieve the user profile corresponding to the authorization code. // Note: if you want to automatically invalidate the authorization code // 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 authorization code 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, including the scopes originally granted. var ticket = await CreateTicketAsync(request, info); 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 <AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), OpenIddictServerDefaults.AuthenticationScheme); if (!request.IsRefreshTokenGrantType() && request.IsAuthorizationCodeGrantType()) { // Set the list of scopes granted to the client application. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); ticket.SetResources("resource-server"); } foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null) { var principal = await _signInManager.CreateUserPrincipalAsync(user); var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); } ticket.SetResources("resource_server"); foreach (var claim in ticket.Principal.Claims) { if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIddictConstants.Claims.Roles && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
private async Task <AuthenticationTicket> _CreateTicketAsync( OpenIdConnectRequest request, AppUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. //Add roles as claim var userWithRoles = _db.Users.Include("Roles").Single(u => u.UserName == user.UserName); if (userWithRoles.Roles.Any()) { var identity = principal.Identity as ClaimsIdentity; var roleNames = _db.Roles.Where(r => userWithRoles.Roles.Select(ur => ur.RoleId).Contains(r.Id)).Select(r => r.Name).ToArray(); foreach (var roleName in roleNames) { identity.AddClaim("role", roleName, new string[] { OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken }); } principal = new ClaimsPrincipal(identity); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); } return(ticket); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the authorization code/refresh token. var result = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = CreateTicket(request, result, result.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } throw new NotSupportedException("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.IsAuthorizationCodeGrantType()) { // Retrieve the claims principal stored in the authorization code. var info = await HttpContext.Authentication.GetAuthenticateInfoAsync( OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code. var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The authorization code 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, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); 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([ModelBinder(BinderType = typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request) { if (request.IsAuthorizationCodeGrantType()) { // Retrieve the claims principal stored in the authorization code. var info = await HttpContext.Authentication.GetAuthenticateInfoAsync( OpenIddictServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code. // Note: if you want to automatically invalidate the authorization code // 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 authorization code 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, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); 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 AuthenticationTicket CreateTicket( OpenIdConnectRequest request, AuthenticateResult result, AuthenticationProperties properties = null) { // Create a new ClaimsIdentity containing the claims that // will be used to create an id_token, a token or a code. var identity = new ClaimsIdentity( result.Principal.Claims, OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), properties, OpenIdConnectServerDefaults.AuthenticationScheme); // Set the list of scopes granted to the client application. if (request.IsAuthorizationRequest() || (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())) { ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OfflineAccess, OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Address, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Phone, OpenIdConnectConstants.Scopes.Profile }.Intersect(request.GetScopes())); } // The OP-Req-acr_values test consists in sending an "acr_values=1 2" parameter // as part of the authorization request. To indicate to the certification client // that the "1" reference value was satisfied, an "acr" claim is added. if (request.IsAuthorizationRequest() && request.HasAcrValue("1")) { identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.AuthenticationContextReference, "1")); } foreach (var claim in identity.Claims) { claim.SetDestinations(destinations: GetDestinations(claim, ticket)); } return(ticket); }
private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, AppUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. /* * I remove this becuase i don't change default claims types */ var principal = await _signInManager.CreateUserPrincipalAsync(user); ((ClaimsIdentity)principal.Identity).AddClaim(new Claim(OpenIddictConstants.Claims.Name, user.UserName)); ((ClaimsIdentity)principal.Identity).AddClaim(new Claim(OpenIddictConstants.Claims.Subject, user.Id)); var roles = ((ClaimsIdentity)principal.Identity).Claims .Where(c => c.ValueType == ClaimTypes.Role); foreach (var role in roles) { ((ClaimsIdentity)principal.Identity) .AddClaim(new Claim(OpenIddictConstants.Claims.Role, role.Value)); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { // Note: in this sample, the granted scopes match the requested scope // but you may want to allow the user to uncheck specific scopes. // For that, simply restrict the list of scopes before calling SetScopes. ticket.SetScopes(request.GetScopes()); ticket.SetResources("resource_server"); } foreach (var claim in ticket.Principal.Claims) { claim.SetDestinations(GetDestinations(claim, ticket)); } return(ticket); }
private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in principal.Claims) { // In this sample, every claim is serialized in both the access and the identity tokens. // In a real world application, you'd probably want to exclude confidential claims // or apply a claims policy based on the scopes requested by the client application. claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); } ticket.SetResources("resource_server"); return(ticket); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { if (request.IsPasswordGrantType()) { return(await PasswordGrantRequest(request)); } else if (request.IsRefreshTokenGrantType()) { return(await RefreshGrantRequest(request)); } else if (request.IsAuthorizationCodeGrantType()) { return(await AuthorizationGrantRequest(request)); } else { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); } }
public async Task <IActionResult> Exchange([ModelBinder(typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request) { if (request.IsAuthorizationCodeGrantType()) { var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme); var user = await userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Срок действия авторизационного кода истёк" })); } if (!await signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Пользователь больше не может войти в систему" })); } var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "Указанный тип авторизации не поддерживается" })); }
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 <AuthenticationTicket> CreateTicketAsync( IUser user, object application, object authorization, OpenIdConnectRequest request, AuthenticationProperties properties = null) { Debug.Assert(request.IsAuthorizationRequest() || request.IsTokenRequest(), "The request should be an authorization or token request."); // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); var identity = (ClaimsIdentity)principal.Identity; // Note: while ASP.NET Core Identity uses the legacy WS-Federation claims (exposed by the ClaimTypes class), // OpenIddict uses the newer JWT claims defined by the OpenID Connect specification. To ensure the mandatory // subject claim is correctly populated (and avoid an InvalidOperationException), it's manually added here. if (string.IsNullOrEmpty(principal.FindFirstValue(OpenIddictConstants.Claims.Subject))) { identity.AddClaim(new Claim(OpenIddictConstants.Claims.Subject, await _userManager.GetUserIdAsync(user))); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (request.IsAuthorizationRequest() || (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(request.GetScopes()); ticket.SetResources(await GetResourcesAsync(request.GetScopes())); // If the request is an authorization request, automatically create // a permanent authorization to avoid requiring explicit consent for // future authorization or token requests containing the same scopes. if (authorization == null && request.IsAuthorizationRequest()) { authorization = await _authorizationManager.CreateAsync( principal : ticket.Principal, subject : await _userManager.GetUserIdAsync(user), client : await _applicationManager.GetIdAsync(application), type : OpenIddictConstants.AuthorizationTypes.Permanent, scopes : ImmutableArray.CreateRange(ticket.GetScopes()), properties : ImmutableDictionary.CreateRange(ticket.Properties.Items)); } if (authorization != null) { // Attach the authorization identifier to the authentication ticket. ticket.SetProperty(OpenIddictConstants.Properties.InternalAuthorizationId, await _authorizationManager.GetIdAsync(authorization)); } } // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIddictConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIddictConstants.Claims.Name && ticket.HasScope(OpenIddictConstants.Scopes.Profile)) || (claim.Type == OpenIddictConstants.Claims.Email && ticket.HasScope(OpenIddictConstants.Scopes.Email)) || (claim.Type == OpenIddictConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIddictConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
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)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { 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." })); } 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." })); } var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } if (request.IsRefreshTokenGrantType()) { var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme); 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." })); } if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } if (request.IsAuthorizationCodeGrantType()) { var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme); 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." })); } if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } var ticket = await CreateTicketAsync(request, user, info.Properties); 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 <string> SerializeIdentityTokenAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Replace the principal by a new one containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). principal = principal.Clone(claim => { // Never exclude the subject claim. if (string.Equals(claim.Type, OpenIdConnectConstants.Claims.Subject, StringComparison.OrdinalIgnoreCase)) { return(true); } // Claims whose destination is not explicitly referenced or doesn't // contain "id_token" are not included in the identity token. if (!claim.HasDestination(OpenIdConnectConstants.Destinations.IdentityToken)) { Logger.LogDebug("'{Claim}' was excluded from the identity token claims.", claim.Type); return(false); } return(true); }); // Remove the destinations from the claim properties. foreach (var claim in principal.Claims) { claim.Properties.Remove(OpenIdConnectConstants.Properties.Destinations); } var identity = (ClaimsIdentity)principal.Identity; // Create a new ticket containing the updated properties and the filtered principal. var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetIdentityTokenLifetime() ?? Options.IdentityTokenLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken); // Associate a random identifier with the identity token. ticket.SetTicketId(Guid.NewGuid().ToString()); // Remove the unwanted properties from the authentication ticket. ticket.RemoveProperty(OpenIdConnectConstants.Properties.AccessTokenLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.ClientId) .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallenge) .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod) .RemoveProperty(OpenIdConnectConstants.Properties.IdentityTokenLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.RedirectUri) .RemoveProperty(OpenIdConnectConstants.Properties.RefreshTokenLifetime); ticket.SetAudiences(ticket.GetPresenters()); var notification = new SerializeIdentityTokenContext(Context, Options, request, response, ticket) { Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.IdentityTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault(key => key.Key is AsymmetricSecurityKey) }; await Options.Provider.SerializeIdentityToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.IdentityToken)) { return(notification.IdentityToken); } else if (notification.Skipped) { return(null); } if (!ReferenceEquals(ticket, notification.Ticket)) { throw new InvalidOperationException("The authentication ticket cannot be replaced."); } if (notification.SecurityTokenHandler == null) { return(null); } // Extract the main identity from the principal. identity = (ClaimsIdentity)ticket.Principal.Identity; if (string.IsNullOrEmpty(identity.GetClaim(OpenIdConnectConstants.Claims.Subject))) { throw new InvalidOperationException("The authentication ticket was rejected because " + "it doesn't contain the mandatory subject claim."); } // Note: identity tokens must be signed but an exception is made by the OpenID Connect specification // when they are returned from the token endpoint: in this case, signing is not mandatory, as the TLS // server validation can be used as a way to ensure an identity token was issued by a trusted party. // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information. if (notification.SigningCredentials == null && request.IsAuthorizationRequest()) { throw new InvalidOperationException("A signing key must be provided."); } // Store the "unique_id" property as a claim. identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTicketId()); // Store the "usage" property as a claim. identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // Store the "confidentiality_level" property as a claim. var confidentiality = ticket.GetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel); if (!string.IsNullOrEmpty(confidentiality)) { identity.AddClaim(OpenIdConnectConstants.Claims.ConfidentialityLevel, confidentiality); } // Store the audiences as claims. foreach (var audience in notification.Audiences) { identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // If a nonce was present in the authorization request, it MUST // be included in the id_token generated by the token endpoint. // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation var nonce = request.Nonce; if (request.IsAuthorizationCodeGrantType()) { // Restore the nonce stored in the authentication // ticket extracted from the authorization code. nonce = ticket.GetProperty(OpenIdConnectConstants.Properties.Nonce); } if (!string.IsNullOrEmpty(nonce)) { identity.AddClaim(OpenIdConnectConstants.Claims.Nonce, nonce); } if (notification.SigningCredentials != null && (!string.IsNullOrEmpty(response.Code) || !string.IsNullOrEmpty(response.AccessToken))) { using (var algorithm = OpenIdConnectServerHelpers.GetHashAlgorithm(notification.SigningCredentials.Algorithm)) { // Create an authorization code hash if necessary. if (!string.IsNullOrEmpty(response.Code)) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.Code)); // Note: only the left-most half of the hash of the octets is used. // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken identity.AddClaim(OpenIdConnectConstants.Claims.CodeHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } // Create an access token hash if necessary. if (!string.IsNullOrEmpty(response.AccessToken)) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.AccessToken)); // Note: only the left-most half of the hash of the octets is used. // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken identity.AddClaim(OpenIdConnectConstants.Claims.AccessTokenHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } } } // Extract the presenters from the authentication ticket. var presenters = notification.Presenters.ToArray(); switch (presenters.Length) { case 0: break; case 1: identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; default: Logger.LogWarning("Multiple presenters have been associated with the identity token " + "but the JWT format only accepts single values."); // Only add the first authorized party. identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; } var token = notification.SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor { Subject = identity, Issuer = notification.Issuer, SigningCredentials = notification.SigningCredentials, IssuedAt = notification.Ticket.Properties.IssuedUtc?.UtcDateTime, NotBefore = notification.Ticket.Properties.IssuedUtc?.UtcDateTime, Expires = notification.Ticket.Properties.ExpiresUtc?.UtcDateTime }); return(notification.SecurityTokenHandler.WriteToken(token)); }
protected override bool IsValid(OpenIdConnectRequest request) { return(request.IsAuthorizationCodeGrantType()); }
public async Task <IActionResult> Exchange(string device_code, OpenIdConnectRequest request, CancellationToken cancellationToken) { 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.IsDeviceCodeGrantType()) { if (request.ClientId == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "Missing required parameter client_id." })); } // exchange device code for tokens var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new ErrorViewModel { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "Details concerning the calling client application cannot be found in the database" })); } var code = await _deviceCodeManager.FindByDeviceCodeAsync(device_code); if (code == null) { return(BadRequest(new ErrorViewModel { Error = OpenIdConnectConstants.Errors.AccessDenied, ErrorDescription = "Access denied" // todo: use canonical descriptions message for these errors })); } if (code.AuthorizedOn == default(DateTimeOffset)) { if (code.LastPolledAt == default(DateTimeOffset)) { code.LastPolledAt = DateTimeOffset.Now; await _deviceCodeManager.UpdateLastPolledAt(code); } else { var interval = DateTimeOffset.Now - code.LastPolledAt; if (interval.TotalMilliseconds < _deviceCodeOptions.Interval * 950) { return(BadRequest(new ErrorViewModel { Error = DeviceCodeFlowConstants.Errors.SlowDown, ErrorDescription = "Slow down" })); } else { code.LastPolledAt = DateTimeOffset.Now; await _deviceCodeManager.UpdateLastPolledAt(code); } } return(BadRequest(new ErrorViewModel { Error = DeviceCodeFlowConstants.Errors.DeviceCodeAuthorizationPending, ErrorDescription = "Device code authorization pending" })); } var user = await _userManager.FindByIdAsync(code.Subject); if (user == null) { return(BadRequest(new ErrorViewModel { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal error has occurred" })); } await _deviceCodeManager.Consume(code); // 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." })); } var ticket = await CreateTicketAsync(new OpenIdConnectRequest { Scope = string.Join(" ", code.Scopes), ClientId = request.ClientId, ClientSecret = request.ClientSecret, GrantType = request.GrantType }, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsPasswordGrantType()) { // not implemented } else if (request.IsAuthorizationCodeGrantType()) { // Retrieve the claims principal stored in the authorization code. var info = await HttpContext.Authentication.GetAuthenticateInfoAsync( OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code. var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The authorization code 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, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); 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 = "The username/password couple is invalid." })); } // Ensure the user is allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Reject the token request if two-factor authentication has been enabled by the user. if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Ensure the user is not already locked out. if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Ensure the password is valid. if (!await _userManager.CheckPasswordAsync(user, request.Password)) { if (_userManager.SupportsUserLockout) { await _userManager.AccessFailedAsync(user); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } if (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user); } // 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.Authentication.GetAuthenticateInfoAsync( OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. 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)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
private async Task <AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, User user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); } // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in principal.Claims) { if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } ticket.Properties.AllowRefresh = true; // var roles = principal.Claims.Where(c => c.Type == ClaimTypes.Role).ToList(); // ticket.Properties.Items.Add(new KeyValuePair<string, string>("roles", string.Join(", ", roles.Select(r => r.Value)))); // ticket.Properties.ExpiresUtc = DateTimeOffset.Now.AddDays(14); // ticket.Properties.IsPersistent = false; await UpdateIpAddressForUser(user.Id); return(ticket); }
private async Task <string> SerializeIdentityTokenAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Replace the identity by a new one containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). identity = identity.Clone(claim => { // Never exclude ClaimTypes.NameIdentifier. if (string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase)) { return(true); } // Claims whose destination is not explicitly referenced or doesn't // contain "id_token" are not included in the identity token. return(claim.HasDestination(OpenIdConnectConstants.Destinations.IdentityToken)); }); // Create a new ticket containing the updated properties and the filtered identity. var ticket = new AuthenticationTicket(identity, properties); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetIdentityTokenLifetime() ?? Options.IdentityTokenLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken); // Associate a random identifier with the identity token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the identity token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetAudiences(request.ClientId); ticket.SetPresenters(request.ClientId); } var notification = new SerializeIdentityTokenContext(Context, Options, request, response, ticket) { Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.IdentityTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeIdentityToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.IdentityToken)) { return(notification.IdentityToken); } else if (notification.Skipped) { return(null); } if (notification.SecurityTokenHandler == null) { return(null); } if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject) && !identity.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier)) { throw new InvalidOperationException("A unique identifier cannot be found to generate a 'sub' claim: " + "make sure to add a 'ClaimTypes.NameIdentifier' claim."); } if (notification.SigningCredentials == null) { throw new InvalidOperationException("A signing key must be provided."); } // Store the unique subject identifier as a claim. if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject)) { identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identity.GetClaim(ClaimTypes.NameIdentifier)); } // Remove the ClaimTypes.NameIdentifier claims to avoid getting duplicate claims. // Note: the "sub" claim is automatically mapped by JwtSecurityTokenHandler // to ClaimTypes.NameIdentifier when validating a JWT token. // Note: make sure to call ToArray() to avoid an InvalidOperationException // on old versions of Mono, where FindAll() is implemented using an iterator. foreach (var claim in identity.FindAll(ClaimTypes.NameIdentifier).ToArray()) { identity.RemoveClaim(claim); } // Store the "unique_id" property as a claim. ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTicketId()); // Store the "usage" property as a claim. ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // Store the "confidentiality_level" property as a claim. var confidentiality = ticket.GetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel); if (!string.IsNullOrEmpty(confidentiality)) { identity.AddClaim(OpenIdConnectConstants.Claims.ConfidentialityLevel, confidentiality); } // Store the audiences as claims. foreach (var audience in notification.Audiences) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // If a nonce was present in the authorization request, it MUST // be included in the id_token generated by the token endpoint. // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation var nonce = request.Nonce; if (request.IsAuthorizationCodeGrantType()) { // Restore the nonce stored in the authentication // ticket extracted from the authorization code. nonce = ticket.GetProperty(OpenIdConnectConstants.Properties.Nonce); } if (!string.IsNullOrEmpty(nonce)) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Nonce, nonce); } using (var algorithm = HashAlgorithm.Create(notification.SigningCredentials.DigestAlgorithm)) { // Create an authorization code hash if necessary. if (!string.IsNullOrEmpty(response.Code)) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.Code)); // Note: only the left-most half of the hash of the octets is used. // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken identity.AddClaim(OpenIdConnectConstants.Claims.CodeHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } // Create an access token hash if necessary. if (!string.IsNullOrEmpty(response.AccessToken)) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.AccessToken)); // Note: only the left-most half of the hash of the octets is used. // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken identity.AddClaim(OpenIdConnectConstants.Claims.AccessTokenHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } } // Extract the presenters from the authentication ticket. var presenters = notification.Presenters.ToArray(); switch (presenters.Length) { case 0: break; case 1: identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; default: Options.Logger.LogWarning("Multiple presenters have been associated with the identity token " + "but the JWT format only accepts single values."); // Only add the first authorized party. identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; } if (ticket.Properties.IssuedUtc != null) { ticket.Identity.AddClaim(new Claim( OpenIdConnectConstants.Claims.IssuedAt, EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime).ToString(), ClaimValueTypes.Integer64)); } var descriptor = new SecurityTokenDescriptor { Subject = ticket.Identity, TokenIssuerName = notification.Issuer, SigningCredentials = notification.SigningCredentials, Lifetime = new Lifetime( notification.Ticket.Properties.IssuedUtc?.UtcDateTime, notification.Ticket.Properties.ExpiresUtc?.UtcDateTime) }; var token = notification.SecurityTokenHandler.CreateToken(descriptor); return(notification.SecurityTokenHandler.WriteToken(token)); }
public async Task <IActionResult> Token(OpenIdConnectRequest request) { if (request.IsPasswordGrantType()) { var user = await _userManager.FindByEmailAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid" })); } if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in" })); } if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "This user has exceeded max number of sign in attemps" })); } if (!await _userManager.CheckPasswordAsync(user, request.Password)) { if (_userManager.SupportsUserLockout) { await _userManager.AccessFailedAsync(user); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid" })); } if (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user); } var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { var info = await HttpContext.Authentication.GetAuthenticateInfoAsync( OpenIdConnectServerDefaults.AuthenticationScheme); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token is no longer valid" })); } if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "This user is no longer allowed to sign in" })); } var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "Specified grant type is not supported" })); }