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); }
public async Task <IActionResult> Authorize() { var response = HttpContext.GetOpenIddictServerResponse(); if (response != null) { return(View("Error", new ErrorViewModel { Error = response.Error, ErrorDescription = response.ErrorDescription })); } var request = HttpContext.GetOpenIddictServerRequest(); if (request == null) { return(NotFound()); } // Retrieve the claims stored in the authentication cookie. // If they can't be extracted, redirect the user to the login page. var result = await HttpContext.AuthenticateAsync(); if (result == null || !result.Succeeded || request.HasPrompt(Prompts.Login)) { return(RedirectToLoginPage(request)); } // If a max_age parameter was provided, ensure that the cookie is not too old. // If it's too old, automatically redirect the user agent to the login page. if (request.MaxAge != null && result.Properties.IssuedUtc != null && DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)) { return(RedirectToLoginPage(request)); } var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? throw new InvalidOperationException("The application details cannot be found."); var authorizations = await _authorizationManager.FindAsync( subject : result.Principal.GetUserIdentifier(), client : await _applicationManager.GetIdAsync(application), status : Statuses.Valid, type : AuthorizationTypes.Permanent, scopes : request.GetScopes()).ToListAsync(); switch (await _applicationManager.GetConsentTypeAsync(application)) { case ConsentTypes.External when !authorizations.Any(): return(Forbid(new AuthenticationProperties(new Dictionary <string, string> { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = S["The logged in user is not allowed to access this client application."] }), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); case ConsentTypes.Implicit: case ConsentTypes.External when authorizations.Any(): case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): var identity = new ClaimsIdentity(result.Principal.Claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); identity.AddClaim(OpenIdConstants.Claims.EntityType, OpenIdConstants.EntityTypes.User, Destinations.AccessToken, Destinations.IdentityToken); // 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(result.Principal.FindFirst(Claims.Subject)?.Value)) { identity.AddClaim(new Claim(Claims.Subject, result.Principal.GetUserIdentifier())); } principal.SetScopes(request.GetScopes()); principal.SetResources(await GetResourcesAsync(request.GetScopes())); // Automatically create a permanent authorization to avoid requiring explicit consent // for future authorization or token requests containing the same scopes. var authorization = authorizations.LastOrDefault(); if (authorization == null) { authorization = await _authorizationManager.CreateAsync( principal : principal, subject : principal.GetUserIdentifier(), client : await _applicationManager.GetIdAsync(application), type : AuthorizationTypes.Permanent, scopes : principal.GetScopes()); } principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); foreach (var claim in principal.Claims) { claim.SetDestinations(GetDestinations(claim, principal)); } return(SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): return(Forbid(new AuthenticationProperties(new Dictionary <string, string> { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = S["Interactive user consent is required."] }), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); default: return(View(new AuthorizeViewModel { ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application), RequestId = request.RequestId, Scope = request.Scope })); } IActionResult RedirectToLoginPage(OpenIddictRequest request) { // If the client application requested promptless authentication, // return an error indicating that the user is not logged in. if (request.HasPrompt(Prompts.None)) { return(Forbid(new AuthenticationProperties(new Dictionary <string, string> { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = S["The user is not logged in."] }), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); } string GetRedirectUrl() { // Override the prompt parameter to prevent infinite authentication/authorization loops. var parameters = Request.Query.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); parameters[Parameters.Prompt] = "continue"; return(Request.PathBase + Request.Path + QueryString.Create(parameters)); } return(Challenge(new AuthenticationProperties { RedirectUri = GetRedirectUrl() })); } }