private async Task <string> SerializeAccessTokenAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Create a new principal 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 "access_token" are not included in the access token. if (!claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken)) { Logger.LogDebug("'{Claim}' was excluded from the access 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, Scheme.Name); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; // Only set the expiration date if a lifetime was specified in either the ticket or the options. var lifetime = ticket.GetAccessTokenLifetime() ?? Options.AccessTokenLifetime; if (lifetime.HasValue) { ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + lifetime.Value; } // Associate a random identifier with the access token. ticket.SetTokenId(Guid.NewGuid().ToString()); ticket.SetAudiences(ticket.GetResources()); // Remove the unwanted properties from the authentication ticket. ticket.RemoveProperty(OpenIdConnectConstants.Properties.AccessTokenLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallenge) .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod) .RemoveProperty(OpenIdConnectConstants.Properties.IdentityTokenLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.Nonce) .RemoveProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri) .RemoveProperty(OpenIdConnectConstants.Properties.RefreshTokenLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.TokenUsage); var notification = new SerializeAccessTokenContext(Context, Scheme, Options, request, response, ticket) { DataFormat = Options.AccessTokenFormat, EncryptingCredentials = Options.EncryptingCredentials.FirstOrDefault( credentials => credentials.Key is SymmetricSecurityKey), Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault( credentials => credentials.Key is SymmetricSecurityKey) ?? Options.SigningCredentials.FirstOrDefault() }; await Provider.SerializeAccessToken(notification); if (notification.IsHandled || !string.IsNullOrEmpty(notification.AccessToken)) { return(notification.AccessToken); } if (notification.SecurityTokenHandler == null) { if (notification.DataFormat == null) { throw new InvalidOperationException("A security token handler or data formatter must be provided."); } var value = notification.DataFormat.Protect(ticket); Logger.LogTrace("A new access token was successfully generated using the " + "specified data format: {Token} ; {Claims} ; {Properties}.", value, ticket.Principal.Claims, ticket.Properties.Items); return(value); } // At this stage, throw an exception if no signing credentials were provided. if (notification.SigningCredentials == null) { throw new InvalidOperationException("A signing key must be provided."); } // Extract the main identity from the principal. identity = (ClaimsIdentity)ticket.Principal.Identity; // Store the "usage" property as a claim. identity.AddClaim(OpenIdConnectConstants.Claims.TokenUsage, OpenIdConnectConstants.TokenUsages.AccessToken); // Store the "unique_id" property as a claim. identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTokenId()); // 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); } // Create a new claim per scope item, that will result // in a "scope" array being added in the access token. foreach (var scope in notification.Scopes) { identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope); } // Store the audiences as claims. foreach (var audience in notification.Audiences) { identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // 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 access 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.CreateEncodedJwt(new SecurityTokenDescriptor { Subject = identity, Issuer = notification.Issuer, EncryptingCredentials = notification.EncryptingCredentials, SigningCredentials = notification.SigningCredentials, IssuedAt = notification.Ticket.Properties.IssuedUtc?.UtcDateTime, NotBefore = notification.Ticket.Properties.IssuedUtc?.UtcDateTime, Expires = notification.Ticket.Properties.ExpiresUtc?.UtcDateTime }); Logger.LogTrace("A new access token was successfully generated using the specified " + "security token handler: {Token} ; {Claims} ; {Properties}.", token, ticket.Principal.Claims, ticket.Properties.Items); return(token); }
/// <summary> /// Represents an event called when serializing an access token. /// </summary> /// <param name="context">The context instance associated with this event.</param> /// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns> public virtual Task SerializeAccessToken(SerializeAccessTokenContext context) => OnSerializeAccessToken(context);