Browse Source

Allow determining the token format dynamically and automatically add a jti claim to JWT access tokens

pull/1449/head
Kévin Chalet 4 years ago
parent
commit
7bb02a43bd
  1. 14
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  2. 1
      src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj
  3. 3
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionExtensions.cs
  4. 30
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlerFilters.cs
  5. 98
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs
  6. 4
      src/OpenIddict.Client/OpenIddictClientBuilder.cs
  7. 5
      src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs
  8. 1
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  9. 16
      src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
  10. 3
      src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
  11. 21
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  12. 1
      src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj
  13. 3
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs
  14. 30
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs
  15. 179
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs
  16. 4
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  17. 5
      src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
  18. 1
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  19. 16
      src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
  20. 25
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  21. 8
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  22. 2
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  23. 21
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs
  24. 2
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
  25. 2
      src/OpenIddict.Validation/OpenIddictValidationOptions.cs

14
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -228,7 +228,7 @@ public static class OpenIddictConstants
public static class JsonWebTokenTypes public static class JsonWebTokenTypes
{ {
public const string AccessToken = "at+jwt"; public const string AccessToken = "at+jwt";
public const string JsonWebToken = "JWT"; public const string Jwt = "JWT";
public static class Prefixes public static class Prefixes
{ {
@ -489,6 +489,18 @@ public static class OpenIddictConstants
public const string Public = "public"; public const string Public = "public";
} }
public static class TokenFormats
{
public const string Jwt = "urn:ietf:params:oauth:token-type:jwt";
public const string Saml1 = "urn:ietf:params:oauth:token-type:saml1";
public const string Saml2 = "urn:ietf:params:oauth:token-type:saml2";
public static class Private
{
public const string DataProtection = "urn:openiddict:params:oauth:token-type:dp";
}
}
public static class TokenTypeHints public static class TokenTypeHints
{ {
public const string AccessToken = "access_token"; public const string AccessToken = "access_token";

1
src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj

@ -33,6 +33,7 @@
<Using Include="OpenIddict.Client.OpenIddictClientHandlers" Static="true" /> <Using Include="OpenIddict.Client.OpenIddictClientHandlers" Static="true" />
<Using Include="OpenIddict.Client.OpenIddictClientHandlerFilters" Static="true" /> <Using Include="OpenIddict.Client.OpenIddictClientHandlerFilters" Static="true" />
<Using Include="OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionHandlers" Static="true" /> <Using Include="OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionHandlers" Static="true" />
<Using Include="OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionHandlerFilters" Static="true" />
</ItemGroup> </ItemGroup>
</Project> </Project>

3
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionExtensions.cs

@ -36,6 +36,9 @@ public static class OpenIddictClientDataProtectionExtensions
// Note: the order used here is not important, as the actual order is set in the options. // Note: the order used here is not important, as the actual order is set in the options.
builder.Services.TryAdd(OpenIddictClientDataProtectionHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); builder.Services.TryAdd(OpenIddictClientDataProtectionHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Register the built-in filters used by the default OpenIddict Data Protection event handlers.
builder.Services.TryAddSingleton<RequireDataProtectionTokenFormat>();
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[] builder.Services.TryAddEnumerable(new[]
{ {

30
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlerFilters.cs

@ -0,0 +1,30 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
namespace OpenIddict.Client.DataProtection;
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static class OpenIddictClientDataProtectionHandlerFilters
{
/// <summary>
/// Represents a filter that excludes the associated handlers if
/// the selected token format is not ASP.NET Core Data Protection.
/// </summary>
public class RequireDataProtectionTokenFormat : IOpenIddictClientHandlerFilter<GenerateTokenContext>
{
public ValueTask<bool> IsActiveAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.TokenFormat is TokenFormats.Private.DataProtection);
}
}
}

98
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs

@ -29,6 +29,7 @@ public static partial class OpenIddictClientDataProtectionHandlers
/* /*
* Token generation: * Token generation:
*/ */
OverrideGeneratedTokenFormat.Descriptor,
GenerateDataProtectionToken.Descriptor); GenerateDataProtectionToken.Descriptor);
/// <summary> /// <summary>
@ -100,15 +101,18 @@ public static partial class OpenIddictClientDataProtectionHandlers
ClaimsPrincipal? ValidateToken(string type) ClaimsPrincipal? ValidateToken(string type)
{ {
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch //
{ // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
TokenTypeHints.StateToken when !string.IsNullOrEmpty(context.TokenId) (type, context.TokenId) switch
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server }, {
TokenTypeHints.StateToken => new[] { Handlers.Client, Formats.StateToken, Schemes.Server }, (TokenTypeHints.StateToken, { Length: not 0 })
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.StateToken, null or { Length: 0 })
=> new[] { Handlers.Client, Formats.StateToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
}); });
try try
{ {
@ -130,6 +134,53 @@ public static partial class OpenIddictClientDataProtectionHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for overriding the default token format
/// to generate ASP.NET Core Data Protection tokens instead of JSON Web Tokens.
/// </summary>
public class OverrideGeneratedTokenFormat : IOpenIddictClientHandler<GenerateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictClientDataProtectionOptions> _options;
public OverrideGeneratedTokenFormat(IOptionsMonitor<OpenIddictClientDataProtectionOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.UseSingletonHandler<OverrideGeneratedTokenFormat>()
.SetOrder(AttachSecurityCredentials.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// ASP.NET Core Data Protection can be used to format certain types of tokens in lieu
// of the default token format (typically, JSON Web Token). By default, Data Protection
// is automatically used for all the supported token types once the integration is enabled
// but the default token format can be re-enabled in the options. Alternatively, the token
// format can be overriden manually using a custom event handler registered after this one.
context.TokenFormat = context.TokenType switch
{
TokenTypeHints.StateToken when !_options.CurrentValue.PreferDefaultStateTokenFormat
=> TokenFormats.Private.DataProtection,
_ => context.TokenFormat // Don't override the format if the token type is not supported.
};
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for generating a token using Data Protection. /// Contains the logic responsible for generating a token using Data Protection.
/// </summary> /// </summary>
@ -145,6 +196,7 @@ public static partial class OpenIddictClientDataProtectionHandlers
/// </summary> /// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireDataProtectionTokenFormat>()
.UseSingletonHandler<GenerateDataProtectionToken>() .UseSingletonHandler<GenerateDataProtectionToken>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
@ -164,27 +216,19 @@ public static partial class OpenIddictClientDataProtectionHandlers
return default; return default;
} }
if (context.TokenType switch
{
TokenTypeHints.StateToken => _options.CurrentValue.PreferDefaultStateTokenFormat,
// The token type is not supported by the Data Protection integration (e.g client assertion tokens).
_ => true
})
{
return default;
}
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch //
{ // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
TokenTypeHints.StateToken when !context.Options.DisableTokenStorage (context.TokenType, context.PersistTokenPayload) switch
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server }, {
TokenTypeHints.StateToken => new[] { Handlers.Client, Formats.StateToken, Schemes.Server }, (TokenTypeHints.StateToken, true)
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.StateToken, false)
=> new[] { Handlers.Client, Formats.StateToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
}); });
using var buffer = new MemoryStream(); using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer); using var writer = new BinaryWriter(buffer);

4
src/OpenIddict.Client/OpenIddictClientBuilder.cs

@ -299,7 +299,7 @@ public class OpenIddictClientBuilder
=> AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), => AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058))
}; };
static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) static SymmetricSecurityKey CreateSymmetricSecurityKey(int size)
@ -741,7 +741,7 @@ public class OpenIddictClientBuilder
=> throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)), => throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)),
#endif #endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058))
}; };
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",

5
src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs

@ -71,6 +71,11 @@ public static partial class OpenIddictClientEvents
/// </summary> /// </summary>
public string? Token { get; set; } public string? Token { get; set; }
/// <summary>
/// Gets or sets the format of the token (e.g JWT or ASP.NET Core Data Protection) to create.
/// </summary>
public string TokenFormat { get; set; } = default!;
/// <summary> /// <summary>
/// Gets or sets the type of the token to create. /// Gets or sets the type of the token to create.
/// </summary> /// </summary>

1
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -45,6 +45,7 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton<RequireFrontchannelAccessTokenValidated>(); builder.Services.TryAddSingleton<RequireFrontchannelAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireFrontchannelIdentityTokenValidated>(); builder.Services.TryAddSingleton<RequireFrontchannelIdentityTokenValidated>();
builder.Services.TryAddSingleton<RequireFrontchannelIdentityTokenPrincipal>(); builder.Services.TryAddSingleton<RequireFrontchannelIdentityTokenPrincipal>();
builder.Services.TryAddSingleton<RequireJsonWebTokenFormat>();
builder.Services.TryAddSingleton<RequireRedirectionRequest>(); builder.Services.TryAddSingleton<RequireRedirectionRequest>();
builder.Services.TryAddSingleton<RequireRefreshTokenValidated>(); builder.Services.TryAddSingleton<RequireRefreshTokenValidated>();
builder.Services.TryAddSingleton<RequireStateTokenGenerated>(); builder.Services.TryAddSingleton<RequireStateTokenGenerated>();

16
src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

@ -156,6 +156,22 @@ public static class OpenIddictClientHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if the selected token format is not JSON Web Token.
/// </summary>
public class RequireJsonWebTokenFormat : IOpenIddictClientHandlerFilter<GenerateTokenContext>
{
public ValueTask<bool> IsActiveAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.TokenFormat is TokenFormats.Jwt);
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if the request is not a redirection request. /// Represents a filter that excludes the associated handlers if the request is not a redirection request.
/// </summary> /// </summary>

3
src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs

@ -707,6 +707,7 @@ public static partial class OpenIddictClientHandlers
/// </summary> /// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireJsonWebTokenFormat>()
.UseSingletonHandler<GenerateIdentityModelToken>() .UseSingletonHandler<GenerateIdentityModelToken>()
.SetOrder(CreateTokenEntry.Descriptor.Order + 1_000) .SetOrder(CreateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
@ -776,7 +777,7 @@ public static partial class OpenIddictClientHandlers
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),
// For client assertion tokens, use the generic "JWT" type. // For client assertion tokens, use the generic "JWT" type.
TokenTypeHints.ClientAssertionToken => JsonWebTokenTypes.JsonWebToken, TokenTypeHints.ClientAssertionToken => JsonWebTokenTypes.Jwt,
// For state tokens, use its private representation. // For state tokens, use its private representation.
TokenTypeHints.StateToken => JsonWebTokenTypes.Private.StateToken, TokenTypeHints.StateToken => JsonWebTokenTypes.Private.StateToken,

21
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -1661,7 +1661,16 @@ public static partial class OpenIddictClientHandlers
// //
// See https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication // See https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
// and https://datatracker.ietf.org/doc/html/rfc7523#section-3 for more information. // and https://datatracker.ietf.org/doc/html/rfc7523#section-3 for more information.
principal.SetAudiences(context.TokenEndpoint!.OriginalString); if (!string.IsNullOrEmpty(context.TokenEndpoint?.OriginalString))
{
principal.SetAudiences(context.TokenEndpoint.OriginalString);
}
// If the token endpoint address is not available, use the issuer address as the audience.
else
{
principal.SetAudiences(context.Issuer.OriginalString);
}
// Use the client_id as both the subject and the issuer, as required by the specifications. // Use the client_id as both the subject and the issuer, as required by the specifications.
// //
@ -1714,6 +1723,7 @@ public static partial class OpenIddictClientHandlers
CreateTokenEntry = false, CreateTokenEntry = false,
PersistTokenPayload = false, PersistTokenPayload = false,
Principal = context.ClientAssertionTokenPrincipal!, Principal = context.ClientAssertionTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.ClientAssertionToken TokenType = TokenTypeHints.ClientAssertionToken
}; };
@ -1741,7 +1751,13 @@ public static partial class OpenIddictClientHandlers
} }
context.ClientAssertionToken = notification.Token; context.ClientAssertionToken = notification.Token;
context.ClientAssertionTokenType = ClientAssertionTypes.JwtBearer; context.ClientAssertionTokenType = notification.TokenFormat switch
{
TokenFormats.Jwt => ClientAssertionTypes.JwtBearer,
TokenFormats.Saml2 => ClientAssertionTypes.Saml2Bearer,
_ => null
};
} }
} }
@ -4124,6 +4140,7 @@ public static partial class OpenIddictClientHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.StateTokenPrincipal!, Principal = context.StateTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.StateToken TokenType = TokenTypeHints.StateToken
}; };

1
src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj

@ -33,6 +33,7 @@
<Using Include="OpenIddict.Server.OpenIddictServerHandlers" Static="true" /> <Using Include="OpenIddict.Server.OpenIddictServerHandlers" Static="true" />
<Using Include="OpenIddict.Server.OpenIddictServerHandlerFilters" Static="true" /> <Using Include="OpenIddict.Server.OpenIddictServerHandlerFilters" Static="true" />
<Using Include="OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlers" Static="true" /> <Using Include="OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlers" Static="true" />
<Using Include="OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlerFilters" Static="true" />
</ItemGroup> </ItemGroup>
</Project> </Project>

3
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs

@ -36,6 +36,9 @@ public static class OpenIddictServerDataProtectionExtensions
// Note: the order used here is not important, as the actual order is set in the options. // Note: the order used here is not important, as the actual order is set in the options.
builder.Services.TryAdd(OpenIddictServerDataProtectionHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); builder.Services.TryAdd(OpenIddictServerDataProtectionHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Register the built-in filters used by the default OpenIddict Data Protection event handlers.
builder.Services.TryAddSingleton<RequireDataProtectionTokenFormat>();
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[] builder.Services.TryAddEnumerable(new[]
{ {

30
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs

@ -0,0 +1,30 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
namespace OpenIddict.Server.DataProtection;
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static class OpenIddictServerDataProtectionHandlerFilters
{
/// <summary>
/// Represents a filter that excludes the associated handlers if
/// the selected token format is not ASP.NET Core Data Protection.
/// </summary>
public class RequireDataProtectionTokenFormat : IOpenIddictServerHandlerFilter<GenerateTokenContext>
{
public ValueTask<bool> IsActiveAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.TokenFormat is TokenFormats.Private.DataProtection);
}
}
}

179
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs

@ -29,6 +29,7 @@ public static partial class OpenIddictServerDataProtectionHandlers
/* /*
* Token generation: * Token generation:
*/ */
OverrideGeneratedTokenFormat.Descriptor,
GenerateDataProtectionToken.Descriptor); GenerateDataProtectionToken.Descriptor);
/// <summary> /// <summary>
@ -185,31 +186,38 @@ public static partial class OpenIddictServerDataProtectionHandlers
ClaimsPrincipal? ValidateToken(string type) ClaimsPrincipal? ValidateToken(string type)
{ {
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch //
{ // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) (type, context.TokenId) switch
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, {
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, (TokenTypeHints.AccessToken, { Length: not 0 })
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AuthorizationCode when !string.IsNullOrEmpty(context.TokenId) (TokenTypeHints.AccessToken, null or { Length: 0 })
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
(TokenTypeHints.AuthorizationCode, { Length: not 0 })
TokenTypeHints.DeviceCode when !string.IsNullOrEmpty(context.TokenId) => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, (TokenTypeHints.AuthorizationCode, null or { Length: 0 })
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
TokenTypeHints.RefreshToken when !string.IsNullOrEmpty(context.TokenId) (TokenTypeHints.DeviceCode, { Length: not 0 })
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, (TokenTypeHints.DeviceCode, null or { Length: 0 })
=> new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server },
TokenTypeHints.UserCode when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, (TokenTypeHints.RefreshToken, { Length: not 0 })
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.RefreshToken, null or { Length: 0 })
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server },
});
(TokenTypeHints.UserCode, { Length: not 0 })
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.UserCode, null or { Length: 0 })
=> new[] { Handlers.Server, Formats.UserCode, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try try
{ {
@ -231,6 +239,65 @@ public static partial class OpenIddictServerDataProtectionHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for overriding the default token format
/// to generate ASP.NET Core Data Protection tokens instead of JSON Web Tokens.
/// </summary>
public class OverrideGeneratedTokenFormat : IOpenIddictServerHandler<GenerateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public OverrideGeneratedTokenFormat(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.UseSingletonHandler<OverrideGeneratedTokenFormat>()
.SetOrder(AttachSecurityCredentials.Descriptor.Order + 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// ASP.NET Core Data Protection can be used to format certain types of tokens in lieu
// of the default token format (typically, JSON Web Token). By default, Data Protection
// is automatically used for all the supported token types once the integration is enabled
// but the default token format can be re-enabled in the options. Alternatively, the token
// format can be overriden manually using a custom event handler registered after this one.
context.TokenFormat = context.TokenType switch
{
TokenTypeHints.AccessToken when !_options.CurrentValue.PreferDefaultAccessTokenFormat
=> TokenFormats.Private.DataProtection,
TokenTypeHints.AuthorizationCode when !_options.CurrentValue.PreferDefaultAuthorizationCodeFormat
=> TokenFormats.Private.DataProtection,
TokenTypeHints.DeviceCode when !_options.CurrentValue.PreferDefaultDeviceCodeFormat
=> TokenFormats.Private.DataProtection,
TokenTypeHints.RefreshToken when !_options.CurrentValue.PreferDefaultRefreshTokenFormat
=> TokenFormats.Private.DataProtection,
TokenTypeHints.UserCode when !_options.CurrentValue.PreferDefaultUserCodeFormat
=> TokenFormats.Private.DataProtection,
_ => context.TokenFormat // Don't override the format if the token type is not supported.
};
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for generating a token using Data Protection. /// Contains the logic responsible for generating a token using Data Protection.
/// </summary> /// </summary>
@ -246,6 +313,7 @@ public static partial class OpenIddictServerDataProtectionHandlers
/// </summary> /// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireDataProtectionTokenFormat>()
.UseSingletonHandler<GenerateDataProtectionToken>() .UseSingletonHandler<GenerateDataProtectionToken>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
@ -265,46 +333,39 @@ public static partial class OpenIddictServerDataProtectionHandlers
return default; return default;
} }
if (context.TokenType switch
{
TokenTypeHints.AccessToken => _options.CurrentValue.PreferDefaultAccessTokenFormat,
TokenTypeHints.AuthorizationCode => _options.CurrentValue.PreferDefaultAuthorizationCodeFormat,
TokenTypeHints.DeviceCode => _options.CurrentValue.PreferDefaultDeviceCodeFormat,
TokenTypeHints.RefreshToken => _options.CurrentValue.PreferDefaultRefreshTokenFormat,
TokenTypeHints.UserCode => _options.CurrentValue.PreferDefaultUserCodeFormat,
_ => true // The token type is not supported by the Data Protection integration (e.g identity tokens).
})
{
return default;
}
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch //
{ // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
TokenTypeHints.AccessToken when context.Options.UseReferenceAccessTokens (context.TokenType, context.PersistTokenPayload) switch
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, {
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, (TokenTypeHints.AccessToken, true)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.AccessToken, false)
=> new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage (TokenTypeHints.AuthorizationCode, true)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, (TokenTypeHints.AuthorizationCode, false)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
TokenTypeHints.DeviceCode when !context.Options.DisableTokenStorage (TokenTypeHints.DeviceCode, true)
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, (TokenTypeHints.DeviceCode, false)
=> new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server },
TokenTypeHints.RefreshToken when context.Options.UseReferenceRefreshTokens (TokenTypeHints.RefreshToken, true)
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, (TokenTypeHints.RefreshToken, false)
=> new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server },
TokenTypeHints.UserCode when !context.Options.DisableTokenStorage (TokenTypeHints.UserCode, true)
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, (TokenTypeHints.UserCode, false)
=> new[] { Handlers.Server, Formats.UserCode, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
}); });
using var buffer = new MemoryStream(); using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer); using var writer = new BinaryWriter(buffer);

4
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -308,7 +308,7 @@ public class OpenIddictServerBuilder
=> AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), => AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058))
}; };
static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) static SymmetricSecurityKey CreateSymmetricSecurityKey(int size)
@ -750,7 +750,7 @@ public class OpenIddictServerBuilder
=> throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)), => throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)),
#endif #endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058))
}; };
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",

5
src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs

@ -77,6 +77,11 @@ public static partial class OpenIddictServerEvents
/// </summary> /// </summary>
public string? Token { get; set; } public string? Token { get; set; }
/// <summary>
/// Gets or sets the format of the token (e.g JWT or ASP.NET Core Data Protection) to create.
/// </summary>
public string TokenFormat { get; set; } = default!;
/// <summary> /// <summary>
/// Gets or sets the type of the token to create. /// Gets or sets the type of the token to create.
/// </summary> /// </summary>

1
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -59,6 +59,7 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton<RequireIdentityTokenGenerated>(); builder.Services.TryAddSingleton<RequireIdentityTokenGenerated>();
builder.Services.TryAddSingleton<RequireIdentityTokenValidated>(); builder.Services.TryAddSingleton<RequireIdentityTokenValidated>();
builder.Services.TryAddSingleton<RequireIntrospectionRequest>(); builder.Services.TryAddSingleton<RequireIntrospectionRequest>();
builder.Services.TryAddSingleton<RequireJsonWebTokenFormat>();
builder.Services.TryAddSingleton<RequireLogoutRequest>(); builder.Services.TryAddSingleton<RequireLogoutRequest>();
builder.Services.TryAddSingleton<RequirePostLogoutRedirectUriParameter>(); builder.Services.TryAddSingleton<RequirePostLogoutRedirectUriParameter>();
builder.Services.TryAddSingleton<RequireReferenceAccessTokensEnabled>(); builder.Services.TryAddSingleton<RequireReferenceAccessTokensEnabled>();

16
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -315,6 +315,22 @@ public static class OpenIddictServerHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if the selected token format is not JSON Web Token.
/// </summary>
public class RequireJsonWebTokenFormat : IOpenIddictServerHandlerFilter<GenerateTokenContext>
{
public ValueTask<bool> IsActiveAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.TokenFormat is TokenFormats.Jwt);
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if the request is not a logout request. /// Represents a filter that excludes the associated handlers if the request is not a logout request.
/// </summary> /// </summary>

25
src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

@ -90,8 +90,8 @@ public static partial class OpenIddictServerHandlers
// For identity tokens, both "JWT" and "application/jwt" are valid. // For identity tokens, both "JWT" and "application/jwt" are valid.
TokenTypeHints.IdToken => new[] TokenTypeHints.IdToken => new[]
{ {
JsonWebTokenTypes.JsonWebToken, JsonWebTokenTypes.Jwt,
JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.JsonWebToken JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.Jwt
}, },
// For authorization codes, only the short "oi_auc+jwt" form is valid. // For authorization codes, only the short "oi_auc+jwt" form is valid.
@ -369,7 +369,7 @@ public static partial class OpenIddictServerHandlers
=> TokenTypeHints.AccessToken, => TokenTypeHints.AccessToken,
// Both JWT and application/JWT are supported for identity tokens. // Both JWT and application/JWT are supported for identity tokens.
JsonWebTokenTypes.JsonWebToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.JsonWebToken JsonWebTokenTypes.Jwt or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.Jwt
=> TokenTypeHints.IdToken, => TokenTypeHints.IdToken,
JsonWebTokenTypes.Private.AuthorizationCode => TokenTypeHints.AuthorizationCode, JsonWebTokenTypes.Private.AuthorizationCode => TokenTypeHints.AuthorizationCode,
@ -424,7 +424,7 @@ public static partial class OpenIddictServerHandlers
// To ensure access tokens generated by previous versions are still correctly handled, // To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported. // both formats (unique space-separated string or multiple scope claims) must be supported.
// To achieve that, all the "scope" claims are combined into a single one containg all the values. // To achieve that, all the "scope" claims are combined into a single one containg all the values.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. // Visit https://datatracker.ietf.org/doc/html/rfc9068 for more information.
var scopes = context.Principal.GetClaims(Claims.Scope); var scopes = context.Principal.GetClaims(Claims.Scope);
if (scopes.Length > 1) if (scopes.Length > 1)
{ {
@ -1145,6 +1145,7 @@ public static partial class OpenIddictServerHandlers
/// </summary> /// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireJsonWebTokenFormat>()
.UseSingletonHandler<GenerateIdentityModelToken>() .UseSingletonHandler<GenerateIdentityModelToken>()
.SetOrder(CreateTokenEntry.Descriptor.Order + 1_000) .SetOrder(CreateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
@ -1202,10 +1203,16 @@ public static partial class OpenIddictServerHandlers
} }
} }
// For access tokens, set the public scope claim using the private scope claims from the principal. // For access tokens, set the public scope claim using the private scope
// Note: scopes are deliberately formatted as a single space-separated // claims from the principal and add a jti claim containing a random identifier
// (separate from the token identifier used by OpenIddict to attach a database
// entry to the token) that can be used by the resource servers to determine
// whether an access token has already been used or blacklist them if necessary.
//
// Note: scopes are deliberately formatted as a single space-separated
// string to respect the usual representation of the standard scope claim. // string to respect the usual representation of the standard scope claim.
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04. //
// See https://datatracker.ietf.org/doc/html/rfc9068 for more information.
if (context.TokenType is TokenTypeHints.AccessToken) if (context.TokenType is TokenTypeHints.AccessToken)
{ {
var scopes = context.Principal.GetScopes(); var scopes = context.Principal.GetScopes();
@ -1213,6 +1220,8 @@ public static partial class OpenIddictServerHandlers
{ {
claims.Add(Claims.Scope, string.Join(" ", scopes)); claims.Add(Claims.Scope, string.Join(" ", scopes));
} }
claims.Add(Claims.JwtId, Guid.NewGuid().ToString());
} }
// For authorization/device/user codes and refresh tokens, // For authorization/device/user codes and refresh tokens,
@ -1241,7 +1250,7 @@ public static partial class OpenIddictServerHandlers
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),
TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.JsonWebToken, TokenTypeHints.IdToken => JsonWebTokenTypes.Jwt,
TokenTypeHints.AuthorizationCode => JsonWebTokenTypes.Private.AuthorizationCode, TokenTypeHints.AuthorizationCode => JsonWebTokenTypes.Private.AuthorizationCode,
TokenTypeHints.DeviceCode => JsonWebTokenTypes.Private.DeviceCode, TokenTypeHints.DeviceCode => JsonWebTokenTypes.Private.DeviceCode,
TokenTypeHints.RefreshToken => JsonWebTokenTypes.Private.RefreshToken, TokenTypeHints.RefreshToken => JsonWebTokenTypes.Private.RefreshToken,

8
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -1661,7 +1661,7 @@ public static partial class OpenIddictServerHandlers
principal.SetAudiences(context.Principal.GetResources()); principal.SetAudiences(context.Principal.GetResources());
// Store the client identifier in the public client_id claim, if available. // Store the client identifier in the public client_id claim, if available.
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. // See https://datatracker.ietf.org/doc/html/rfc9068 for more information.
principal.SetClaim(Claims.ClientId, context.ClientId); principal.SetClaim(Claims.ClientId, context.ClientId);
// When receiving a grant_type=refresh_token request, determine whether the client application // When receiving a grant_type=refresh_token request, determine whether the client application
@ -2223,6 +2223,7 @@ public static partial class OpenIddictServerHandlers
// corresponding option was enabled in the server options. // corresponding option was enabled in the server options.
PersistTokenPayload = context.Options.UseReferenceAccessTokens, PersistTokenPayload = context.Options.UseReferenceAccessTokens,
Principal = context.AccessTokenPrincipal!, Principal = context.AccessTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.AccessToken TokenType = TokenTypeHints.AccessToken
}; };
@ -2288,6 +2289,7 @@ public static partial class OpenIddictServerHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.AuthorizationCodePrincipal!, Principal = context.AuthorizationCodePrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.AuthorizationCode TokenType = TokenTypeHints.AuthorizationCode
}; };
@ -2360,6 +2362,7 @@ public static partial class OpenIddictServerHandlers
_ => !context.Options.DisableTokenStorage _ => !context.Options.DisableTokenStorage
}, },
Principal = context.DeviceCodePrincipal!, Principal = context.DeviceCodePrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.DeviceCode TokenType = TokenTypeHints.DeviceCode
}; };
@ -2427,6 +2430,7 @@ public static partial class OpenIddictServerHandlers
// corresponding option was enabled in the server options. // corresponding option was enabled in the server options.
PersistTokenPayload = context.Options.UseReferenceRefreshTokens, PersistTokenPayload = context.Options.UseReferenceRefreshTokens,
Principal = context.RefreshTokenPrincipal!, Principal = context.RefreshTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.RefreshToken TokenType = TokenTypeHints.RefreshToken
}; };
@ -2731,6 +2735,7 @@ public static partial class OpenIddictServerHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.UserCodePrincipal!, Principal = context.UserCodePrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.UserCode TokenType = TokenTypeHints.UserCode
}; };
@ -2797,6 +2802,7 @@ public static partial class OpenIddictServerHandlers
// Identity tokens cannot never be reference tokens. // Identity tokens cannot never be reference tokens.
PersistTokenPayload = false, PersistTokenPayload = false,
Principal = context.IdentityTokenPrincipal!, Principal = context.IdentityTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.IdToken TokenType = TokenTypeHints.IdToken
}; };

2
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -143,7 +143,7 @@ public class OpenIddictServerOptions
type = usage switch type = usage switch
{ {
TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.JsonWebToken, TokenTypeHints.IdToken => JsonWebTokenTypes.Jwt,
_ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269)) _ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269))
}; };

21
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs

@ -95,15 +95,18 @@ public static partial class OpenIddictValidationDataProtectionHandlers
ClaimsPrincipal? ValidateToken(string type) ClaimsPrincipal? ValidateToken(string type)
{ {
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch //
{ // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) (type, context.TokenId) switch
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, {
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, (TokenTypeHints.AccessToken, { Length: not 0 })
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) (TokenTypeHints.AccessToken, null or { Length: 0 })
}); => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try try
{ {

2
src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs

@ -413,7 +413,7 @@ public static partial class OpenIddictValidationHandlers
// To ensure access tokens generated by previous versions are still correctly handled, // To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported. // both formats (unique space-separated string or multiple scope claims) must be supported.
// To achieve that, all the "scope" claims are combined into a single one containg all the values. // To achieve that, all the "scope" claims are combined into a single one containg all the values.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. // Visit https://datatracker.ietf.org/doc/html/rfc9068 for more information.
var scopes = context.Principal.GetClaims(Claims.Scope); var scopes = context.Principal.GetClaims(Claims.Scope);
if (scopes.Length > 1) if (scopes.Length > 1)
{ {

2
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -128,7 +128,7 @@ public class OpenIddictValidationOptions
type = usage switch type = usage switch
{ {
TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.JsonWebToken, TokenTypeHints.IdToken => JsonWebTokenTypes.Jwt,
_ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269)) _ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269))
}; };

Loading…
Cancel
Save