diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs index 2c740ed0..dd6879ad 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs @@ -139,7 +139,7 @@ namespace OpenIddict.Server.DataProtection // Note: since the data format relies on a data protector using different "purposes" strings // per token type, the token processed at this stage is guaranteed to be of the expected type. - return _options.CurrentValue.Formatter.ReadToken(reader)?.SetClaim(Claims.Private.TokenType, type); + return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); } catch (Exception exception) diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 3932bfa8..22c6603c 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -483,7 +483,7 @@ namespace OpenIddict.Server context.Principal = new ClaimsPrincipal(result.ClaimsIdentity); // Store the token type as a special private claim. - context.Principal.SetClaim(Claims.Private.TokenType, token.Typ switch + context.Principal.SetTokenType(token.Typ switch { JsonWebTokenTypes.AccessToken => TokenTypeHints.AccessToken, JsonWebTokenTypes.IdentityToken => TokenTypeHints.IdToken, @@ -634,7 +634,7 @@ namespace OpenIddict.Server .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) .SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) .SetInternalTokenId(await _tokenManager.GetIdAsync(token)) - .SetClaim(Claims.Private.TokenType, await _tokenManager.GetTypeAsync(token)); + .SetTokenType(await _tokenManager.GetTypeAsync(token)); } } @@ -693,6 +693,31 @@ namespace OpenIddict.Server return default; } + // When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel + // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). + // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, + // which requires that a special claim containing the token type be present in the security principal. + if (!string.IsNullOrEmpty(context.TokenType)) + { + var type = context.Principal.GetTokenType(); + if (string.IsNullOrEmpty(type)) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The deserialized principal doesn't contain the mandatory 'oi_tkn_typ' claim.") + .Append("When implementing custom token deserialization, a 'oi_tkn_typ' claim containing ") + .Append("the type of the token being processed must be added to the security principal.") + .ToString()); + } + + if (!string.Equals(type, context.TokenType, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException(new StringBuilder() + .AppendFormat("The type of token associated with the deserialized principal ({0})", type) + .AppendFormat("doesn't match the expected token type ({0}).", context.TokenType) + .ToString()); + } + } + return default; } } @@ -872,7 +897,7 @@ namespace OpenIddict.Server .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) .SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) .SetInternalTokenId(await _tokenManager.GetIdAsync(token)) - .SetClaim(Claims.Private.TokenType, await _tokenManager.GetTypeAsync(token)); + .SetTokenType(await _tokenManager.GetTypeAsync(token)); async ValueTask TryRevokeAuthorizationChainAsync(string identifier) { diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs index 175c0957..6d226292 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs @@ -86,7 +86,9 @@ namespace OpenIddict.Validation.DataProtection using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token))); using var reader = new BinaryReader(buffer); - principal = _options.CurrentValue.Formatter.ReadToken(reader); + // Note: since the data format relies on a data protector using different "purposes" strings + // per token type, the token processed at this stage is guaranteed to be of the expected type. + principal = _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(TokenTypeHints.AccessToken); } catch (Exception exception) @@ -100,10 +102,6 @@ namespace OpenIddict.Validation.DataProtection return default; } - // Note: since the data format relies on a data protector using different "purposes" strings - // per token type, the token processed at this stage is guaranteed to be of the expected type. - context.Principal = principal.SetClaim(Claims.Private.TokenType, TokenTypeHints.AccessToken); - context.Logger.LogTrace("The self-contained DP token '{Token}' was successfully validated and the following " + "claims could be extracted: {Claims}.", context.Token, context.Principal.Claims); diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs index b1396a10..a1b9f189 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs @@ -218,13 +218,10 @@ namespace OpenIddict.Validation return default; } - // Attach the principal extracted from the token to the parent event context. - context.Principal = new ClaimsPrincipal(result.ClaimsIdentity); - // Note: tokens that are considered valid at this point are guaranteed to be access tokens, // as a "typ" header validation is performed by the JWT handler, based on the valid values // set in the token validation parameters (by default, only "at+jwt" is considered valid). - context.Principal.SetClaim(Claims.Private.TokenType, TokenTypeHints.AccessToken); + context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(TokenTypeHints.AccessToken); context.Logger.LogTrace("The self-contained JWT token '{Token}' was successfully validated and the following " + "claims could be extracted: {Claims}.", context.Token, context.Principal.Claims); @@ -350,7 +347,7 @@ namespace OpenIddict.Validation .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) .SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) .SetInternalTokenId(await _tokenManager.GetIdAsync(token)) - .SetClaim(Claims.Private.TokenType, await _tokenManager.GetTypeAsync(token)); + .SetTokenType(await _tokenManager.GetTypeAsync(token)); } } @@ -391,6 +388,28 @@ namespace OpenIddict.Validation return default; } + // When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel + // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). + // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, + // which requires that a special claim containing the token type be present in the security principal. + var type = context.Principal.GetTokenType(); + if (string.IsNullOrEmpty(type)) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The deserialized principal doesn't contain the mandatory 'oi_tkn_typ' claim.") + .Append("When implementing custom token deserialization, a 'oi_tkn_typ' claim containing ") + .Append("the type of the token being processed must be added to the security principal.") + .ToString()); + } + + if (!string.Equals(type, TokenTypeHints.AccessToken, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException(new StringBuilder() + .AppendFormat("The type of token associated with the deserialized principal ({0})", type) + .AppendFormat("doesn't match the expected token type ({0}).", TokenTypeHints.AccessToken) + .ToString()); + } + return default; } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs index acd2607c..6d31925a 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs @@ -1962,7 +1962,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives var identity = new ClaimsIdentity(); var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.TokenType, type); + principal.SetTokenType(type); // Act and assert Assert.Equal(type, principal.GetTokenType()); @@ -2259,7 +2259,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives var identity = new ClaimsIdentity(); var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.TokenType, TokenTypeHints.AccessToken); + principal.SetTokenType(TokenTypeHints.AccessToken); // Act and assert Assert.True(principal.HasTokenType(TokenTypeHints.AccessToken)); diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs index 08b67d2b..e2e36286 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs @@ -300,6 +300,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -339,6 +340,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -377,6 +379,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters(Enumerable.Empty()) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -417,6 +420,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Contoso") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -456,6 +460,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Contoso") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -495,6 +500,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback"); @@ -536,6 +542,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback"); @@ -578,6 +585,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -618,6 +626,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") @@ -660,6 +669,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") @@ -703,6 +713,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") @@ -748,6 +759,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.CodeChallenge, challenge) @@ -792,6 +804,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Private.CodeChallenge, challenge) @@ -842,6 +855,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetScopes(Enumerable.Empty()) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -883,6 +897,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetScopes("profile", "email") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -924,6 +939,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Enumerable.Empty()) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -963,6 +979,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes("profile", "email") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1676,6 +1693,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1739,6 +1757,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur") @@ -1890,6 +1909,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1952,6 +1972,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2006,6 +2027,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2074,6 +2096,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Fabrikam") .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2138,6 +2161,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2216,6 +2240,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2274,6 +2299,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -2367,6 +2393,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2467,6 +2494,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -2564,6 +2592,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Fabrikam") .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -2636,6 +2665,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -2719,6 +2749,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Fabrikam") .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -2777,6 +2808,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -2873,6 +2905,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2950,6 +2983,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -3045,6 +3079,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") @@ -3136,6 +3171,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -3217,6 +3253,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -3310,6 +3347,7 @@ namespace OpenIddict.Server.FunctionalTests builder.UseInlineHandler(context => { context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(context.TokenType) .SetPresenters("Fabrikam") .SetInternalTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC") .SetClaim(Claims.Subject, "Bob le Bricoleur"); diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs index 7f5dd377..28115c67 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs @@ -197,6 +197,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; @@ -360,6 +361,109 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]) response[Claims.Private.Scope]); } + [Fact] + public async Task ProcessAuthentication_MissingTokenTypeThrowsAnException() + { + // Arrange + var client = CreateClient(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(null) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "access_token" + }); + }); + + // Assert + Assert.Equal(new StringBuilder() + .AppendLine("The deserialized principal doesn't contain the mandatory 'oi_tkn_typ' claim.") + .Append("When implementing custom token deserialization, a 'oi_tkn_typ' claim containing ") + .Append("the type of the token being processed must be added to the security principal.") + .ToString(), exception.Message); + } + + [Fact] + public async Task ProcessAuthentication_InvalidTokenTypeThrowsAnException() + { + // Arrange + var client = CreateClient(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "access_token" + }); + }); + + // Assert + Assert.Equal(new StringBuilder() + .AppendFormat("The type of token associated with the deserialized principal ({0})", TokenTypeHints.AuthorizationCode) + .AppendFormat("doesn't match the expected token type ({0}).", TokenTypeHints.AccessToken) + .ToString(), exception.Message); + } + [Fact] public async Task ProcessAuthentication_MissingIdTokenHintReturnsNull() { @@ -441,6 +545,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.IdToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.IdToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; @@ -545,6 +650,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetClaim(Claims.Subject, "Bob le Magnifique") .SetPresenters("Fabrikam"); @@ -650,6 +756,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; @@ -794,6 +901,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; @@ -1280,6 +1388,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.Profile, Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1411,6 +1520,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1461,6 +1571,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetClaim(Claims.Subject, "Bob le Bricoleur"); return default; @@ -1706,6 +1817,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetScopes(Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1757,6 +1869,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -1807,6 +1920,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2074,6 +2188,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetScopes(Scopes.OpenId) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2125,6 +2240,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2394,6 +2510,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur") .SetClaim(Claims.Prefixes.Private + "_private_claim", "value"); @@ -2443,6 +2560,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2484,6 +2602,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2521,6 +2640,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2573,6 +2693,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2642,6 +2763,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2719,6 +2841,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2782,6 +2905,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2838,6 +2962,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -2918,6 +3043,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") @@ -3004,6 +3130,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") @@ -3077,6 +3204,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -3134,6 +3262,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -3192,6 +3321,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -3254,6 +3384,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur"); @@ -3314,6 +3445,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") .SetClaim(Claims.Subject, "Bob le Bricoleur");