public async Task HandleRevocationRequest_RequestIsRejectedWhenTokenIsAnAccessToken() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SlAV32hkKG")) .Returns(ticket); var server = CreateAuthorizationServer(builder => { builder.Configure(options => options.AccessTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedTokenType, response.Error); Assert.Equal("Only authorization codes and refresh tokens can be revoked.", response.ErrorDescription); format.Verify(mock => mock.Unprotect("SlAV32hkKG"), Times.Once()); }
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenRefreshTokenIsRevoked() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) .Returns(ticket); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny <CancellationToken>())) .ReturnsAsync(true); })); builder.Services.AddSingleton(manager); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(1, response.GetParameters().Count()); Assert.False((bool)response[OpenIdConnectConstants.Claims.Active]); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>()), Times.Once()); }
public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRevokedWhenSlidingExpirationIsEnabled() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103"); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); var token = new OpenIddictToken(); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>())) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); builder.Services.AddSingleton(manager); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken, RefreshToken = "8xLOxBtZp8" }); // Assert Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny <CancellationToken>()), Times.Once()); }
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsExpired() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetPresenters("Fabrikam"); ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA")) .Returns(ticket); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); builder.Services.AddSingleton(manager); builder.Configure(options => options.AuthorizationCodeFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", Code = "SplxlOBeZQQYbYS6WxSbIA", GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode, RedirectUri = "http://www.fabrikam.com/path" }); // Assert Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The authorization code is no longer valid.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>()), Times.Once()); }
private async Task <string> SerializeRefreshTokenAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.RefreshTokenLifetime; } // Claims in refresh tokens are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); // Associate a random identifier with the refresh token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the refresh token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeRefreshTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.RefreshTokenFormat }; await Options.Provider.SerializeRefreshToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.RefreshToken)) { return(notification.RefreshToken); } else if (notification.Skipped) { return(null); } if (!ReferenceEquals(ticket, notification.Ticket)) { throw new InvalidOperationException("The authentication ticket cannot be replaced."); } return(notification.DataFormat?.Protect(ticket)); }
private async Task <string> SerializeAuthorizationCodeAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.AuthorizationCodeLifetime; } // Claims in authorization codes are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. var ticket = new AuthenticationTicket(identity, properties); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); // Associate a random identifier with the authorization code. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the authorization code. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.SerializeAuthorizationCode(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AuthorizationCode)) { return(notification.AuthorizationCode); } else if (notification.Skipped) { return(null); } if (notification.DataFormat == null) { return(null); } return(notification.DataFormat.Protect(ticket)); }
private async Task <string> SerializeAuthorizationCodeAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Note: claims in authorization codes are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. // Create a new ticket containing the updated properties. var ticket = new AuthenticationTicket(identity, properties); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetAuthorizationCodeLifetime() ?? Options.AuthorizationCodeLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); // Associate a random identifier with the authorization code. ticket.SetTicketId(Guid.NewGuid().ToString()); // Store the code_challenge, code_challenge_method and nonce parameters for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, request.CodeChallenge) .SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, request.CodeChallengeMethod) .SetProperty(OpenIdConnectConstants.Properties.Nonce, request.Nonce); // Store the original redirect_uri sent by the client application for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, request.GetProperty <string>(OpenIdConnectConstants.Properties.OriginalRedirectUri)); // Remove the unwanted properties from the authentication ticket. ticket.RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.ClientId); var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.SerializeAuthorizationCode(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AuthorizationCode)) { return(notification.AuthorizationCode); } else if (notification.Skipped) { return(null); } if (notification.DataFormat == null) { return(null); } return(notification.DataFormat.Protect(ticket)); }
public async Task HandleTokenRequest_AuthorizationCodeIsAutomaticallyRevoked() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetPresenters("Fabrikam"); ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA")) .Returns(ticket); var token = Mock.Of <object>(); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56")) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = Mock.Of <object>(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application)) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); builder.Services.AddSingleton(manager); builder.Configure(options => options.AuthorizationCodeFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", Code = "SplxlOBeZQQYbYS6WxSbIA", GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode }); // Assert Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once()); }
private async Task <string> SerializeAuthorizationCodeAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Note: claims in authorization codes are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. // Create a new ticket containing the updated properties. var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetAuthorizationCodeLifetime() ?? Options.AuthorizationCodeLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); // Associate a random identifier with the authorization code. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the authorization code. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.SerializeAuthorizationCode(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AuthorizationCode)) { return(notification.AuthorizationCode); } else if (notification.Skipped) { return(null); } if (!ReferenceEquals(ticket, notification.Ticket)) { throw new InvalidOperationException("The authentication ticket cannot be replaced."); } if (notification.DataFormat == null) { return(null); } return(notification.DataFormat.Protect(ticket)); }
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsExpired() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103"); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); builder.Services.AddSingleton(manager); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken, RefreshToken = "8xLOxBtZp8" }); // Assert Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The refresh token is no longer valid.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>()), Times.Once()); }
private async Task <string> SerializeRefreshTokenAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Note: claims in refresh tokens are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. // Create a new ticket containing the updated properties. var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetRefreshTokenLifetime() ?? Options.RefreshTokenLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); // Associate a random identifier with the refresh token. ticket.SetTicketId(Guid.NewGuid().ToString()); // Remove the unwanted properties from the authentication ticket. ticket.RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.ClientId) .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallenge) .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod) .RemoveProperty(OpenIdConnectConstants.Properties.Nonce) .RemoveProperty(OpenIdConnectConstants.Properties.RedirectUri); var notification = new SerializeRefreshTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.RefreshTokenFormat }; await Options.Provider.SerializeRefreshToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.RefreshToken)) { return(notification.RefreshToken); } else if (notification.Skipped) { return(null); } if (!ReferenceEquals(ticket, notification.Ticket)) { throw new InvalidOperationException("The authentication ticket cannot be replaced."); } return(notification.DataFormat?.Protect(ticket)); }
public void SetTicketId_AddsScopes(string identifier) { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); // Act ticket.SetTicketId(identifier); // Assert Assert.Equal(identifier, ticket.GetProperty(OpenIdConnectConstants.Properties.TicketId)); }
public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenRevocationIsDisabled() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetPresenters("Fabrikam"); ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA")) .Returns(ticket); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); builder.Configure(options => options.AuthorizationCodeFormat = format.Object); builder.Configure(options => options.RevocationEndpointPath = PathString.Empty); builder.DisableTokenRevocation(); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", Code = "SplxlOBeZQQYbYS6WxSbIA", GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode, RedirectUri = "http://www.fabrikam.com/path" }); // Assert Assert.NotNull(response.AccessToken); }
public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenRevocationIsDisabled() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103"); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); builder.Configure(options => options.RefreshTokenFormat = format.Object); builder.Configure(options => options.RevocationEndpointPath = PathString.Empty); builder.DisableTokenRevocation(); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken, RefreshToken = "8xLOxBtZp8" }); // Assert Assert.NotNull(response.AccessToken); }
private async Task <string> SerializeRefreshTokenAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Note: claims in refresh tokens are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. // Create a new ticket containing the updated properties. var ticket = new AuthenticationTicket(identity, properties); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetRefreshTokenLifetime() ?? Options.RefreshTokenLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); // Associate a random identifier with the refresh token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the refresh token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeRefreshTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.RefreshTokenFormat }; await Options.Provider.SerializeRefreshToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.RefreshToken)) { return(notification.RefreshToken); } else if (notification.Skipped) { return(null); } return(notification.DataFormat?.Protect(ticket)); }
public async Task HandleRevocationRequest_TokenIsSuccessfullyRevoked() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SlAV32hkKG")) .Returns(ticket); var token = new OpenIddictToken(); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>())) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(manager); builder.Configure(options => options.AccessTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal(0, response.GetParameters().Count()); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny <CancellationToken>()), Times.Once()); }
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)); }
public async Task HandleTokenRequest_RequestsAreNotHandledLocally(string flow) { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103"); switch (flow) { case OpenIdConnectConstants.GrantTypes.AuthorizationCode: ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); ticket.SetPresenters("Fabrikam"); break; case OpenIdConnectConstants.GrantTypes.RefreshToken: ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken); break; } var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); var token = new OpenIddictToken(); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>())) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny <CancellationToken>())) .ReturnsAsync(true); })); builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); builder.Services.AddSingleton(manager); builder.Configure(options => options.AuthorizationCodeFormat = format.Object); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Code = "8xLOxBtZp8", GrantType = flow, RefreshToken = "8xLOxBtZp8", Username = "******", Password = "******" }); // Assert Assert.NotNull(response.AccessToken); }
private async Task <AuthenticationTicket> DeserializeAccessTokenAsync(string token, OpenIdConnectMessage request) { var notification = new DeserializeAccessTokenContext(Context, Options, request, token) { DataFormat = Options.AccessTokenFormat, Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.DeserializeAccessToken(notification); if (notification.HandledResponse || notification.Ticket != null) { return(notification.Ticket); } else if (notification.Skipped) { return(null); } var handler = notification.SecurityTokenHandler as ISecurityTokenValidator; if (handler == null) { return(notification.DataFormat?.Unprotect(token)); } // Create new validation parameters to validate the security token. // ValidateAudience and ValidateLifetime are always set to false: // if necessary, the audience and the expiration can be validated // in InvokeIntrospectionEndpointAsync or InvokeTokenEndpointAsync. var parameters = new TokenValidationParameters { IssuerSigningKey = notification.SigningCredentials.Key, ValidIssuer = notification.Issuer, ValidateAudience = false, ValidateLifetime = false }; SecurityToken securityToken; ClaimsPrincipal principal; try { principal = handler.ValidateToken(token, parameters, out securityToken); } catch (Exception exception) { Logger.LogDebug("An exception occured when deserializing an identity token: {Message}.", exception.Message); return(null); } // Parameters stored in AuthenticationProperties are lost // when the identity token is serialized using a security token handler. // To mitigate that, they are inferred from the claims or the security token. var properties = new AuthenticationProperties { ExpiresUtc = securityToken.ValidTo, IssuedUtc = securityToken.ValidFrom }; var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); var audiences = principal.FindAll(OpenIdConnectConstants.Claims.Audience); if (audiences.Any()) { ticket.SetAudiences(audiences.Select(claim => claim.Value)); } var presenters = principal.FindAll(OpenIdConnectConstants.Claims.AuthorizedParty); if (presenters.Any()) { ticket.SetPresenters(presenters.Select(claim => claim.Value)); } var scopes = principal.FindAll(OpenIdConnectConstants.Claims.Scope); if (scopes.Any()) { ticket.SetScopes(scopes.Select(claim => claim.Value)); } // Note: the token identifier may be stored in either the token_id claim // or in the jti claim, which is the standard name used by JWT tokens. var identifier = principal.FindFirst(OpenIdConnectConstants.Claims.JwtId) ?? principal.FindFirst(OpenIdConnectConstants.Claims.TokenId); if (identifier != null) { ticket.SetTicketId(identifier.Value); } var usage = principal.FindFirst(OpenIdConnectConstants.Claims.Usage); if (usage != null) { ticket.SetUsage(usage.Value); } var confidential = principal.FindFirst(OpenIdConnectConstants.Claims.Confidential); if (confidential != null && string.Equals(confidential.Value, "true", StringComparison.OrdinalIgnoreCase)) { ticket.Properties.Items[OpenIdConnectConstants.Properties.Confidential] = "true"; } // Ensure that e received ticket is an access token. if (!ticket.IsAccessToken()) { Logger.LogDebug("The received token was not an access token: {Token}.", token); return(null); } return(ticket); }
private async Task <string> SerializeIdentityTokenAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.IdentityTokenLifetime; } // 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 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)); }); 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.SetUsage(OpenIdConnectConstants.Usages.IdToken); // 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 (!string.IsNullOrEmpty(notification.IdentityToken)) { return(notification.IdentityToken); } if (notification.SecurityTokenHandler == null) { return(null); } if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject) && !identity.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier)) { Logger.LogError("A unique identifier cannot be found to generate a 'sub' claim: " + "make sure to add a 'ClaimTypes.NameIdentifier' claim."); return(null); } // Extract the main identity from the principal. identity = (ClaimsIdentity)ticket.Principal.Identity; // 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. identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTicketId()); // Store the "usage" property as a claim. identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // If the ticket is marked as confidential, add a new // "confidential" claim in the security token. if (ticket.IsConfidential()) { identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean)); } // Store the audiences as claims. foreach (var audience in ticket.GetAudiences()) { 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.GetNonce(); } if (!string.IsNullOrEmpty(nonce)) { identity.AddClaim(OpenIdConnectConstants.Claims.Nonce, nonce); } if (!string.IsNullOrEmpty(response.Code)) { using (var algorithm = OpenIdConnectServerHelpers.GetHashAlgorithm(notification.SigningCredentials.Algorithm)) { // Create the c_hash using the authorization code returned by SerializeAuthorizationCodeAsync. 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)); } } if (!string.IsNullOrEmpty(response.AccessToken)) { using (var algorithm = OpenIdConnectServerHelpers.GetHashAlgorithm(notification.SigningCredentials.Algorithm)) { // Create the at_hash using the access token returned by SerializeAccessTokenAsync. 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 = ticket.GetPresenters().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.CreateJwtSecurityToken( subject: identity, issuer: notification.Issuer, signingCredentials: notification.SigningCredentials, issuedAt: ticket.Properties.IssuedUtc.Value.UtcDateTime, notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime, expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime); var x509SecurityKey = notification.SigningCredentials?.Key as X509SecurityKey; if (x509SecurityKey != null) { // Note: unlike "kid", "x5t" is not automatically added by JwtHeader's constructor in IdentityModel for .NET Core. // Though not required by the specifications, this property is needed for IdentityModel for Katana to work correctly. // See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/132 // and https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/181. token.Header[JwtHeaderParameterNames.X5t] = Base64UrlEncoder.Encode(x509SecurityKey.Certificate.GetCertHash()); } return(notification.SecurityTokenHandler.WriteToken(token)); }
private async Task <string> SerializeAccessTokenAsync( ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.AccessTokenLifetime; } // Create a new principal containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). principal = principal.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 "access_token" are not included in the access token. return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken)); }); 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.SetUsage(OpenIdConnectConstants.Usages.AccessToken); ticket.SetAudiences(ticket.GetResources()); // Associate a random identifier with the access token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the access token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.AccessTokenFormat, Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeAccessToken(notification); if (!string.IsNullOrEmpty(notification.AccessToken)) { return(notification.AccessToken); } if (notification.SecurityTokenHandler == null) { return(notification.DataFormat?.Protect(ticket)); } // Extract the main identity from the principal. identity = (ClaimsIdentity)ticket.Principal.Identity; // Store the "unique_id" property as a claim. identity.AddClaim(notification.SecurityTokenHandler is JwtSecurityTokenHandler ? OpenIdConnectConstants.Claims.JwtId : OpenIdConnectConstants.Claims.TokenId, ticket.GetTicketId()); // Store the "usage" property as a claim. identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // If the ticket is marked as confidential, add a new // "confidential" claim in the security token. if (ticket.IsConfidential()) { identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean)); } // Create a new claim per scope item, that will result // in a "scope" array being added in the access token. foreach (var scope in ticket.GetScopes()) { identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope); } var handler = notification.SecurityTokenHandler as JwtSecurityTokenHandler; if (handler != null) { // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim // but the name identifier claim is used as a substitute when it has been explicitly added. // See https://tools.ietf.org/html/rfc7519#section-4.1.2 var subject = identity.FindFirst(OpenIdConnectConstants.Claims.Subject); if (subject == null) { var identifier = identity.FindFirst(ClaimTypes.NameIdentifier); if (identifier != null) { identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value); } } // 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 audiences as claims. foreach (var audience in ticket.GetAudiences()) { identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // Extract the presenters from the authentication ticket. var presenters = ticket.GetPresenters().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 = handler.CreateJwtSecurityToken( subject: identity, issuer: notification.Issuer, signingCredentials: notification.SigningCredentials, issuedAt: ticket.Properties.IssuedUtc.Value.UtcDateTime, notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime, expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime); var x509SecurityKey = notification.SigningCredentials?.Key as X509SecurityKey; if (x509SecurityKey != null) { // Note: unlike "kid", "x5t" is not automatically added by JwtHeader's constructor in IdentityModel for .NET Core. // Though not required by the specifications, this property is needed for IdentityModel for Katana to work correctly. // See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/132 // and https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/181. token.Header[JwtHeaderParameterNames.X5t] = Base64UrlEncoder.Encode(x509SecurityKey.Certificate.GetCertHash()); } return(handler.WriteToken(token)); } else { var token = notification.SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor { Subject = identity, Issuer = notification.Issuer, Audience = notification.Audiences.ElementAtOrDefault(0), SigningCredentials = notification.SigningCredentials, IssuedAt = notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime, NotBefore = notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime, Expires = notification.Ticket.Properties.ExpiresUtc.Value.UtcDateTime }); // Note: the security token is manually serialized to prevent // an exception from being thrown if the handler doesn't implement // the SecurityTokenHandler.WriteToken overload returning a string. var builder = new StringBuilder(); using (var writer = XmlWriter.Create(builder, new XmlWriterSettings { Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true })) { notification.SecurityTokenHandler.WriteToken(writer, token); } return(builder.ToString()); } }
private async Task <string> SerializeAuthorizationCodeAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.AuthorizationCodeLifetime; } // Claims in authorization codes are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. var ticket = new AuthenticationTicket(identity, properties); ticket.SetUsage(OpenIdConnectConstants.Usages.Code); // Associate a random identifier with the authorization code. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the authorization code. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.SerializeAuthorizationCode(notification); if (!string.IsNullOrEmpty(notification.AuthorizationCode)) { return(notification.AuthorizationCode); } if (notification.DataFormat == null) { return(null); } // Note: make sure to use a secure generated string having // enough entropy to prevent cryptanalytic attacks. var key = Options.RandomNumberGenerator.GenerateKey(length: 256 / 8); using (var stream = new MemoryStream()) using (var writter = new StreamWriter(stream)) { writter.Write(notification.DataFormat.Protect(ticket)); writter.Flush(); // Serialize the authorization code. var bytes = stream.ToArray(); // Store the authorization code in the distributed cache. await Options.Cache.SetAsync($"asos-authorization-code:{key}", bytes, new DistributedCacheEntryOptions { AbsoluteExpiration = ticket.Properties.ExpiresUtc }); } return(key); }
private async Task <string> SerializeAccessTokenAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Create a new identity 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 "access_token" are not included in the access token. return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken)); }); // 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.GetAccessTokenLifetime() ?? Options.AccessTokenLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken); ticket.SetAudiences(ticket.GetResources()); // Associate a random identifier with the access token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the access token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.AccessTokenFormat, Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeAccessToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AccessToken)) { return(notification.AccessToken); } else if (notification.Skipped) { return(null); } if (!notification.Audiences.Any()) { Options.Logger.LogInformation("No explicit audience was associated with the access token."); } if (notification.SecurityTokenHandler == null) { return(notification.DataFormat?.Protect(ticket)); } if (notification.SigningCredentials == null) { throw new InvalidOperationException("A signing key must be provided."); } // 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); } // 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) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope); } // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim // but the name identifier claim is used as a substitute when it has been explicitly added. // See https://tools.ietf.org/html/rfc7519#section-4.1.2 var subject = ticket.Identity.FindFirst(OpenIdConnectConstants.Claims.Subject); if (subject == null) { var identifier = ticket.Identity.FindFirst(ClaimTypes.NameIdentifier); if (identifier != null) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value); } } // 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 ticket.Identity.FindAll(ClaimTypes.NameIdentifier).ToArray()) { ticket.Identity.RemoveClaim(claim); } // Store the audiences as claims. foreach (var audience in notification.Audiences) { ticket.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: ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; default: Options.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. ticket.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, EncryptingCredentials = notification.EncryptingCredentials, 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)); }
private async Task <AuthenticationTicket> DeserializeIdentityTokenAsync(string token, OpenIdConnectMessage request) { var notification = new DeserializeIdentityTokenContext(Context, Options, request, token) { Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.IdentityTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.DeserializeIdentityToken(notification); // Directly return the authentication ticket if one // has been provided by DeserializeIdentityToken. if (notification.Ticket != null) { return(notification.Ticket); } if (notification.SecurityTokenHandler == null) { return(null); } // Create new validation parameters to validate the security token. // ValidateAudience and ValidateLifetime are always set to false: // if necessary, the audience and the expiration can be validated // in InvokeIntrospectionEndpointAsync or InvokeTokenEndpointAsync. var parameters = new TokenValidationParameters { IssuerSigningKey = notification.SigningCredentials.SigningKey, ValidIssuer = notification.Issuer, ValidateAudience = false, ValidateLifetime = false }; SecurityToken securityToken; ClaimsPrincipal principal; try { principal = notification.SecurityTokenHandler.ValidateToken(token, parameters, out securityToken); } catch (Exception exception) { Options.Logger.LogInformation("An exception occured when deserializing an identity token: {Message}", exception.Message); return(null); } // Parameters stored in AuthenticationProperties are lost // when the identity token is serialized using a security token handler. // To mitigate that, they are inferred from the claims or the security token. var properties = new AuthenticationProperties { ExpiresUtc = securityToken.ValidTo, IssuedUtc = securityToken.ValidFrom }; var ticket = new AuthenticationTicket((ClaimsIdentity)principal.Identity, properties); var audiences = principal.FindAll(OpenIdConnectConstants.Claims.Audience); if (audiences.Any()) { ticket.SetAudiences(audiences.Select(claim => claim.Value)); } var presenters = principal.FindAll(OpenIdConnectConstants.Claims.AuthorizedParty); if (presenters.Any()) { ticket.SetPresenters(presenters.Select(claim => claim.Value)); } var identifier = principal.FindFirst(OpenIdConnectConstants.Claims.JwtId); if (identifier != null) { ticket.SetTicketId(identifier.Value); } var usage = principal.FindFirst(OpenIdConnectConstants.Claims.Usage); if (usage != null) { ticket.SetUsage(usage.Value); } var confidential = principal.FindFirst(OpenIdConnectConstants.Claims.Confidential); if (confidential != null && string.Equals(confidential.Value, "true", StringComparison.OrdinalIgnoreCase)) { ticket.Properties.Dictionary[OpenIdConnectConstants.Properties.Confidential] = "true"; } // Ensure the received ticket is an identity token. if (!ticket.IsIdentityToken()) { Options.Logger.LogInformation("The received token was not an identity token: {Token}.", token); return(null); } return(ticket); }
private async Task <AuthenticationTicket> DeserializeIdentityTokenAsync(string token, OpenIdConnectRequest request) { var notification = new DeserializeIdentityTokenContext(Context, Options, request, token) { SecurityTokenHandler = Options.IdentityTokenHandler }; // Note: ValidateAudience and ValidateLifetime are always set to false: // if necessary, the audience and the expiration can be validated // in InvokeIntrospectionEndpointAsync or InvokeTokenEndpointAsync. notification.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKeys = Options.SigningCredentials.Select(credentials => credentials.Key), NameClaimType = OpenIdConnectConstants.Claims.Name, RoleClaimType = OpenIdConnectConstants.Claims.Role, ValidIssuer = Context.GetIssuer(Options), ValidateAudience = false, ValidateLifetime = false }; await Options.Provider.DeserializeIdentityToken(notification); if (notification.HandledResponse || notification.Ticket != null) { notification.Ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken); return(notification.Ticket); } else if (notification.Skipped) { return(null); } if (notification.SecurityTokenHandler == null) { return(null); } SecurityToken securityToken; ClaimsPrincipal principal; try { if (!notification.SecurityTokenHandler.CanReadToken(token)) { Logger.LogDebug("The identity token handler refused to read the token: {Token}", token); return(null); } principal = notification.SecurityTokenHandler.ValidateToken(token, notification.TokenValidationParameters, out securityToken); } catch (Exception exception) { Logger.LogDebug("An exception occured when deserializing an identity token: {Message}.", exception.Message); return(null); } // Parameters stored in AuthenticationProperties are lost // when the identity token is serialized using a security token handler. // To mitigate that, they are inferred from the claims or the security token. var properties = new AuthenticationProperties { ExpiresUtc = securityToken.ValidTo, IssuedUtc = securityToken.ValidFrom }; var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); var audiences = principal.FindAll(OpenIdConnectConstants.Claims.Audience); if (audiences.Any()) { ticket.SetAudiences(audiences.Select(claim => claim.Value)); } var presenters = principal.FindAll(OpenIdConnectConstants.Claims.AuthorizedParty); if (presenters.Any()) { ticket.SetPresenters(presenters.Select(claim => claim.Value)); } var identifier = principal.FindFirst(OpenIdConnectConstants.Claims.JwtId); if (identifier != null) { ticket.SetTicketId(identifier.Value); } var usage = principal.FindFirst(OpenIdConnectConstants.Claims.Usage); if (usage != null) { ticket.SetUsage(usage.Value); } var confidentiality = principal.FindFirst(OpenIdConnectConstants.Claims.ConfidentialityLevel); if (confidentiality != null) { ticket.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, confidentiality.Value); } // Ensure the received ticket is an identity token. if (!ticket.IsIdentityToken()) { Logger.LogDebug("The received token was not an identity token: {Token}.", token); return(null); } return(ticket); }
private async Task <string> SerializeAccessTokenAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.AccessTokenLifetime; } // Create a new identity 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 "access_token" are not included in the access token. return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken)); }); // Create a new ticket containing the updated properties and the filtered identity. var ticket = new AuthenticationTicket(identity, properties); ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken); ticket.SetAudiences(ticket.GetResources()); // Associate a random identifier with the access token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the access token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.AccessTokenFormat, Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeAccessToken(notification); if (!string.IsNullOrEmpty(notification.AccessToken)) { return(notification.AccessToken); } if (notification.SecurityTokenHandler == null) { return(notification.DataFormat?.Protect(ticket)); } // Store the "unique_id" property as a claim. ticket.Identity.AddClaim(notification.SecurityTokenHandler is JwtSecurityTokenHandler ? OpenIdConnectConstants.Claims.JwtId : OpenIdConnectConstants.Claims.TokenId, ticket.GetTicketId()); // Store the "usage" property as a claim. ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // If the ticket is marked as confidential, add a new // "confidential" claim in the security token. if (ticket.IsConfidential()) { ticket.Identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean)); } // Create a new claim per scope item, that will result // in a "scope" array being added in the access token. foreach (var scope in ticket.GetScopes()) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope); } var handler = notification.SecurityTokenHandler as JwtSecurityTokenHandler; if (handler != null) { // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim // but the name identifier claim is used as a substitute when it has been explicitly added. // See https://tools.ietf.org/html/rfc7519#section-4.1.2 var subject = identity.FindFirst(OpenIdConnectConstants.Claims.Subject); if (subject == null) { var identifier = identity.FindFirst(ClaimTypes.NameIdentifier); if (identifier != null) { identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value); } } // 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 ticket.Identity.FindAll(ClaimTypes.NameIdentifier).ToArray()) { ticket.Identity.RemoveClaim(claim); } // Store the audiences as claims. foreach (var audience in ticket.GetAudiences()) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // Extract the presenters from the authentication ticket. var presenters = ticket.GetPresenters().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 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 = handler.CreateToken( subject: ticket.Identity, issuer: notification.Issuer, signingCredentials: notification.SigningCredentials, notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime, expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime); token.Payload[OpenIdConnectConstants.Claims.IssuedAt] = EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime); // Try to extract a key identifier from the signing credentials // and add the "kid" property to the JWT header if applicable. LocalIdKeyIdentifierClause clause = null; if (notification.SigningCredentials?.SigningKeyIdentifier != null && notification.SigningCredentials.SigningKeyIdentifier.TryFind(out clause)) { token.Header[JwtHeaderParameterNames.Kid] = clause.LocalId; } return(handler.WriteToken(token)); } else { var descriptor = new SecurityTokenDescriptor { Subject = ticket.Identity, AppliesToAddress = notification.Audiences.ElementAtOrDefault(0), TokenIssuerName = notification.Issuer, EncryptingCredentials = notification.EncryptingCredentials, SigningCredentials = notification.SigningCredentials, Lifetime = new Lifetime( notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime, notification.Ticket.Properties.ExpiresUtc.Value.UtcDateTime) }; // When the encrypting credentials use an asymmetric key, replace them by a // EncryptedKeyEncryptingCredentials instance to generate a symmetric key. if (descriptor.EncryptingCredentials != null && descriptor.EncryptingCredentials.SecurityKey is AsymmetricSecurityKey) { // Note: EncryptedKeyEncryptingCredentials automatically generates an in-memory key // that will be encrypted using the original credentials and added to the resulting token // if the security token handler fully supports token encryption (e.g SAML or SAML2). descriptor.EncryptingCredentials = new EncryptedKeyEncryptingCredentials( wrappingCredentials: notification.EncryptingCredentials, keySizeInBits: 256, encryptionAlgorithm: SecurityAlgorithms.Aes256Encryption); } var token = notification.SecurityTokenHandler.CreateToken(descriptor); // Note: the security token is manually serialized to prevent // an exception from being thrown if the handler doesn't implement // the SecurityTokenHandler.WriteToken overload returning a string. var builder = new StringBuilder(); using (var writer = XmlWriter.Create(builder, new XmlWriterSettings { Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true })) { notification.SecurityTokenHandler.WriteToken(writer, token); } return(builder.ToString()); } }
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, Options.AuthenticationScheme); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetAccessTokenLifetime() ?? Options.AccessTokenLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken); ticket.SetAudiences(ticket.GetResources()); // Associate a random identifier with the access 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.Nonce) .RemoveProperty(OpenIdConnectConstants.Properties.RedirectUri) .RemoveProperty(OpenIdConnectConstants.Properties.RefreshTokenLifetime); var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.AccessTokenFormat, Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault(key => key.Key is SymmetricSecurityKey) ?? Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeAccessToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AccessToken)) { return(notification.AccessToken); } 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(notification.DataFormat?.Protect(ticket)); } // 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 "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); } // 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.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)); }
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)); }