54 changed files with 4977 additions and 4043 deletions
@ -1,131 +0,0 @@ |
|||||
/* |
|
||||
* 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; |
|
||||
using System.ComponentModel; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using static OpenIddict.Server.OpenIddictServerEvents; |
|
||||
|
|
||||
namespace OpenIddict.Server.DataProtection |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Contains a collection of event handler filters commonly used by the Data Protection handlers.
|
|
||||
/// </summary>
|
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|
||||
public static class OpenIddictServerDataProtectionHandlerFilters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents a filter that excludes the associated handlers if OpenIddict
|
|
||||
/// was not configured to issue ASP.NET Core Data Protection access tokens.
|
|
||||
/// </summary>
|
|
||||
public class RequireDataProtectionAccessTokenFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext> |
|
||||
{ |
|
||||
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
|
||||
|
|
||||
public RequireDataProtectionAccessTokenFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
|
||||
=> _options = options; |
|
||||
|
|
||||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|
||||
{ |
|
||||
if (context is null) |
|
||||
{ |
|
||||
throw new ArgumentNullException(nameof(context)); |
|
||||
} |
|
||||
|
|
||||
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultAccessTokenFormat); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Represents a filter that excludes the associated handlers if OpenIddict
|
|
||||
/// was not configured to issue ASP.NET Core Data Protection authorization codes.
|
|
||||
/// </summary>
|
|
||||
public class RequireDataProtectionAuthorizationCodeFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext> |
|
||||
{ |
|
||||
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
|
||||
|
|
||||
public RequireDataProtectionAuthorizationCodeFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
|
||||
=> _options = options; |
|
||||
|
|
||||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|
||||
{ |
|
||||
if (context is null) |
|
||||
{ |
|
||||
throw new ArgumentNullException(nameof(context)); |
|
||||
} |
|
||||
|
|
||||
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultAuthorizationCodeFormat); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Represents a filter that excludes the associated handlers if OpenIddict
|
|
||||
/// was not configured to issue ASP.NET Core Data Protection device codes.
|
|
||||
/// </summary>
|
|
||||
public class RequireDataProtectionDeviceCodeFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext> |
|
||||
{ |
|
||||
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
|
||||
|
|
||||
public RequireDataProtectionDeviceCodeFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
|
||||
=> _options = options; |
|
||||
|
|
||||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|
||||
{ |
|
||||
if (context is null) |
|
||||
{ |
|
||||
throw new ArgumentNullException(nameof(context)); |
|
||||
} |
|
||||
|
|
||||
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultDeviceCodeFormat); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Represents a filter that excludes the associated handlers if OpenIddict
|
|
||||
/// was not configured to issue ASP.NET Core Data Protection refresh tokens.
|
|
||||
/// </summary>
|
|
||||
public class RequireDataProtectionRefreshTokenFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext> |
|
||||
{ |
|
||||
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
|
||||
|
|
||||
public RequireDataProtectionRefreshTokenFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
|
||||
=> _options = options; |
|
||||
|
|
||||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|
||||
{ |
|
||||
if (context is null) |
|
||||
{ |
|
||||
throw new ArgumentNullException(nameof(context)); |
|
||||
} |
|
||||
|
|
||||
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultRefreshTokenFormat); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Represents a filter that excludes the associated handlers if OpenIddict
|
|
||||
/// was not configured to issue ASP.NET Core Data Protection user codes.
|
|
||||
/// </summary>
|
|
||||
public class RequireDataProtectionUserCodeFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext> |
|
||||
{ |
|
||||
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
|
||||
|
|
||||
public RequireDataProtectionUserCodeFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
|
||||
=> _options = options; |
|
||||
|
|
||||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|
||||
{ |
|
||||
if (context is null) |
|
||||
{ |
|
||||
throw new ArgumentNullException(nameof(context)); |
|
||||
} |
|
||||
|
|
||||
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultUserCodeFormat); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,332 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.DataProtection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes; |
||||
|
using static OpenIddict.Server.OpenIddictServerEvents; |
||||
|
using static OpenIddict.Server.OpenIddictServerHandlers.Protection; |
||||
|
using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Server.DataProtection |
||||
|
{ |
||||
|
public static partial class OpenIddictServerDataProtectionHandlers |
||||
|
{ |
||||
|
public static class Protection |
||||
|
{ |
||||
|
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
||||
|
/* |
||||
|
* Token validation: |
||||
|
*/ |
||||
|
ValidateDataProtectionToken.Descriptor, |
||||
|
|
||||
|
/* |
||||
|
* Token validation: |
||||
|
*/ |
||||
|
GenerateDataProtectionToken.Descriptor); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of validating tokens generated using Data Protection.
|
||||
|
/// </summary>
|
||||
|
public class ValidateDataProtectionToken : IOpenIddictServerHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
||||
|
|
||||
|
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
||||
|
=> _options = options; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.UseSingletonHandler<ValidateDataProtectionToken>() |
||||
|
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500) |
||||
|
.SetType(OpenIddictServerHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
// If a principal was already attached, don't overwrite it.
|
||||
|
if (context.Principal is not null) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
|
||||
|
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
|
||||
|
if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal)) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// Tokens generated using ASP.NET Core Data Protection are encrypted by symmetric keys
|
||||
|
// that are derived from both a master key resolved from the key ring and a specific value
|
||||
|
// known as "purpose" that helps ensure that Data Protection payloads can't be decrypted
|
||||
|
// without the correct "purpose" value, which is different for all types of tokens.
|
||||
|
//
|
||||
|
// While offering extensive protection at the cryptographic level, this prevents decrypting
|
||||
|
// unknown tokens without re-executing the entire decryption routine for each type of token
|
||||
|
// considered valid. To speed up this process when supporting multiple types is required,
|
||||
|
// the Data Protection integration relies on the "token_type_hint" parameter specified
|
||||
|
// by the client when it is available (e.g with introspection or revocation requests).
|
||||
|
|
||||
|
var principal = context.ValidTokenTypes.Count switch |
||||
|
{ |
||||
|
// If no valid token type was set, all supported token types are allowed.
|
||||
|
//
|
||||
|
// Note: if a "token_type_hint" was specified by the client, use it to optimize
|
||||
|
// the token decryption lookup but fall back to other types of tokens
|
||||
|
// if the token can't be decrypted using the specified token type hint.
|
||||
|
//
|
||||
|
// In this case, common types (e.g access/refresh tokens) are checked first.
|
||||
|
0 => context.TokenTypeHint switch |
||||
|
{ |
||||
|
TokenTypeHints.AuthorizationCode => |
||||
|
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AccessToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.UserCode), |
||||
|
|
||||
|
TokenTypeHints.DeviceCode => |
||||
|
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AccessToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.UserCode), |
||||
|
|
||||
|
TokenTypeHints.RefreshToken => |
||||
|
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AccessToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.UserCode), |
||||
|
|
||||
|
TokenTypeHints.UserCode => |
||||
|
ValidateToken(context.Token, TokenTypeHints.UserCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AccessToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.DeviceCode), |
||||
|
|
||||
|
_ => |
||||
|
ValidateToken(context.Token, TokenTypeHints.AccessToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ?? |
||||
|
ValidateToken(context.Token, TokenTypeHints.UserCode), |
||||
|
}, |
||||
|
|
||||
|
// If a single valid token type was set, ignore the specified token type hint.
|
||||
|
1 => context.ValidTokenTypes.ElementAt(0) switch |
||||
|
{ |
||||
|
TokenTypeHints.AccessToken => ValidateToken(context.Token, TokenTypeHints.AccessToken), |
||||
|
TokenTypeHints.RefreshToken => ValidateToken(context.Token, TokenTypeHints.RefreshToken), |
||||
|
TokenTypeHints.AuthorizationCode => ValidateToken(context.Token, TokenTypeHints.AuthorizationCode), |
||||
|
TokenTypeHints.DeviceCode => ValidateToken(context.Token, TokenTypeHints.DeviceCode), |
||||
|
TokenTypeHints.UserCode => ValidateToken(context.Token, TokenTypeHints.UserCode), |
||||
|
|
||||
|
_ => null // The token type is not supported by the Data Protection integration (e.g identity tokens).
|
||||
|
}, |
||||
|
|
||||
|
// If multiple valid types were set, use the specified token type hint
|
||||
|
// and select the first non-null token that can be successfully decrypted.
|
||||
|
_ => context.ValidTokenTypes.OrderBy(type => type switch |
||||
|
{ |
||||
|
// If the token type hint corresponds to one of the valid types, test it first.
|
||||
|
string value when value == context.TokenTypeHint => 0, |
||||
|
|
||||
|
TokenTypeHints.AccessToken => 1, |
||||
|
TokenTypeHints.RefreshToken => 2, |
||||
|
TokenTypeHints.AuthorizationCode => 3, |
||||
|
TokenTypeHints.DeviceCode => 4, |
||||
|
TokenTypeHints.UserCode => 5, |
||||
|
|
||||
|
_ => int.MaxValue |
||||
|
}) |
||||
|
.Select(type => type switch |
||||
|
{ |
||||
|
TokenTypeHints.AccessToken => ValidateToken(context.Token, TokenTypeHints.AccessToken), |
||||
|
TokenTypeHints.RefreshToken => ValidateToken(context.Token, TokenTypeHints.RefreshToken), |
||||
|
TokenTypeHints.AuthorizationCode => ValidateToken(context.Token, TokenTypeHints.AuthorizationCode), |
||||
|
TokenTypeHints.DeviceCode => ValidateToken(context.Token, TokenTypeHints.DeviceCode), |
||||
|
TokenTypeHints.UserCode => ValidateToken(context.Token, TokenTypeHints.UserCode), |
||||
|
|
||||
|
_ => null // The token type is not supported by the Data Protection integration (e.g identity tokens).
|
||||
|
}) |
||||
|
.Where(static principal => principal is not null) |
||||
|
.FirstOrDefault() |
||||
|
}; |
||||
|
|
||||
|
if (principal is null) |
||||
|
{ |
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2004), |
||||
|
uri: SR.FormatID8000(SR.ID2004)); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
context.Principal = principal; |
||||
|
|
||||
|
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims); |
||||
|
|
||||
|
return default; |
||||
|
|
||||
|
ClaimsPrincipal? ValidateToken(string token, string type) |
||||
|
{ |
||||
|
// 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.
|
||||
|
TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) |
||||
|
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.AuthorizationCode when !string.IsNullOrEmpty(context.TokenId) |
||||
|
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.DeviceCode when !string.IsNullOrEmpty(context.TokenId) |
||||
|
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.RefreshToken when !string.IsNullOrEmpty(context.TokenId) |
||||
|
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.UserCode when !string.IsNullOrEmpty(context.TokenId) |
||||
|
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, |
||||
|
|
||||
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) |
||||
|
}); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(token))); |
||||
|
using var reader = new BinaryReader(buffer); |
||||
|
|
||||
|
// 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)?.SetTokenType(type); |
||||
|
} |
||||
|
|
||||
|
catch (Exception exception) |
||||
|
{ |
||||
|
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), token); |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of generating a token using Data Protection.
|
||||
|
/// </summary>
|
||||
|
public class GenerateDataProtectionToken : IOpenIddictServerHandler<GenerateTokenContext> |
||||
|
{ |
||||
|
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; |
||||
|
|
||||
|
public GenerateDataProtectionToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) |
||||
|
=> _options = options; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>() |
||||
|
.UseSingletonHandler<GenerateDataProtectionToken>() |
||||
|
.SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500) |
||||
|
.SetType(OpenIddictServerHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(GenerateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
// If an access token was already attached by another handler, don't overwrite it.
|
||||
|
if (!string.IsNullOrEmpty(context.Token)) |
||||
|
{ |
||||
|
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.
|
||||
|
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch |
||||
|
{ |
||||
|
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
|
||||
|
TokenTypeHints.AccessToken when context.Options.UseReferenceAccessTokens |
||||
|
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage |
||||
|
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.DeviceCode when !context.Options.DisableTokenStorage |
||||
|
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.RefreshToken when context.Options.UseReferenceRefreshTokens |
||||
|
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, |
||||
|
|
||||
|
TokenTypeHints.UserCode when !context.Options.DisableTokenStorage |
||||
|
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, |
||||
|
|
||||
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) |
||||
|
}); |
||||
|
|
||||
|
using var buffer = new MemoryStream(); |
||||
|
using var writer = new BinaryWriter(buffer); |
||||
|
|
||||
|
_options.CurrentValue.Formatter.WriteToken(writer, context.Principal); |
||||
|
|
||||
|
context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray())); |
||||
|
|
||||
|
context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, |
||||
|
context.Token, context.Principal.Claims); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Owin.Security; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Server.Owin |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public class OpenIddictServerOwinProperties : AuthenticationProperties |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public OpenIddictServerOwinProperties() |
||||
|
: this(items: null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public OpenIddictServerOwinProperties(IDictionary<string, string?>? items) |
||||
|
: this(items, parameters: null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OpenIddictServerOwinProperties"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="items">State values dictionary to use.</param>
|
||||
|
/// <param name="parameters">Parameters dictionary to use.</param>
|
||||
|
public OpenIddictServerOwinProperties( |
||||
|
IDictionary<string, string?>? items, |
||||
|
IDictionary<string, object?>? parameters) |
||||
|
: base(items) |
||||
|
=> Parameters = parameters ?? new Dictionary<string, object?>(StringComparer.Ordinal); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the collection of parameters passed to the authentication handler.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Note: these properties are not intended for serialization or persistence,
|
||||
|
/// only for flowing data between call sites.
|
||||
|
/// </remarks>
|
||||
|
public IDictionary<string, object?> Parameters { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a parameter from the <see cref="Parameters"/> collection.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The parameter type.</typeparam>
|
||||
|
/// <param name="name">The parameter name.</param>
|
||||
|
/// <returns>The parameter value or a default value if the property is not set.</returns>
|
||||
|
public T? GetParameter<T>(string name) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(name)) |
||||
|
{ |
||||
|
throw new ArgumentException(SR.ID0190, nameof(name)); |
||||
|
} |
||||
|
|
||||
|
return Parameters.TryGetValue(name, out var parameter) && parameter is T value ? value : default; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets a parameter value in the <see cref="Parameters"/> collection.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The parameter type.</typeparam>
|
||||
|
/// <param name="name">The parameter key.</param>
|
||||
|
/// <param name="value">The value to set.</param>
|
||||
|
public void SetParameter<T>(string name, T? value) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(name)) |
||||
|
{ |
||||
|
throw new ArgumentException(SR.ID0190, nameof(name)); |
||||
|
} |
||||
|
|
||||
|
if (value is null) |
||||
|
{ |
||||
|
Parameters.Remove(name); |
||||
|
} |
||||
|
|
||||
|
else |
||||
|
{ |
||||
|
Parameters[name] = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,16 +0,0 @@ |
|||||
/* |
|
||||
* 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. |
|
||||
*/ |
|
||||
|
|
||||
namespace OpenIddict.Server |
|
||||
{ |
|
||||
public static class OpenIddictServerConstants |
|
||||
{ |
|
||||
public static class Properties |
|
||||
{ |
|
||||
public const string ReferenceTokenIdentifier = ".reference_token_identifier"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,129 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Claims; |
||||
|
using Microsoft.IdentityModel.JsonWebTokens; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using OpenIddict.Abstractions; |
||||
|
|
||||
|
namespace OpenIddict.Server |
||||
|
{ |
||||
|
public static partial class OpenIddictServerEvents |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an event called when generating a token.
|
||||
|
/// </summary>
|
||||
|
public class GenerateTokenContext : BaseValidatingContext |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="GenerateTokenContext"/> class.
|
||||
|
/// </summary>
|
||||
|
public GenerateTokenContext(OpenIddictServerTransaction transaction) |
||||
|
: base(transaction) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the request.
|
||||
|
/// </summary>
|
||||
|
public OpenIddictRequest Request |
||||
|
{ |
||||
|
get => Transaction.Request!; |
||||
|
set => Transaction.Request = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security principal used to create the token.
|
||||
|
/// </summary>
|
||||
|
public ClaimsPrincipal Principal { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the encryption credentials used to encrypt the token.
|
||||
|
/// </summary>
|
||||
|
public EncryptingCredentials? EncryptionCredentials { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the signing credentials used to sign the token.
|
||||
|
/// </summary>
|
||||
|
public SigningCredentials? SigningCredentials { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security token handler used to serialize the security principal.
|
||||
|
/// </summary>
|
||||
|
public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the token returned to the client application.
|
||||
|
/// </summary>
|
||||
|
public string? Token { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the type of the token to create.
|
||||
|
/// </summary>
|
||||
|
public string TokenType { get; set; } = default!; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents an event called when validating a token.
|
||||
|
/// </summary>
|
||||
|
public class ValidateTokenContext : BaseValidatingContext |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="ValidateTokenContext"/> class.
|
||||
|
/// </summary>
|
||||
|
public ValidateTokenContext(OpenIddictServerTransaction transaction) |
||||
|
: base(transaction) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the request.
|
||||
|
/// </summary>
|
||||
|
public OpenIddictRequest Request |
||||
|
{ |
||||
|
get => Transaction.Request!; |
||||
|
set => Transaction.Request = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security token handler used to validate the token.
|
||||
|
/// </summary>
|
||||
|
public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the validation parameters used to verify the authenticity of tokens.
|
||||
|
/// </summary>
|
||||
|
public TokenValidationParameters TokenValidationParameters { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the token to validate.
|
||||
|
/// </summary>
|
||||
|
public string Token { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the token type hint specified by the client, if applicable.
|
||||
|
/// </summary>
|
||||
|
public string? TokenTypeHint { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the token entry identifier associated with the token, if applicable.
|
||||
|
/// </summary>
|
||||
|
public string? TokenId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security principal resolved from the token.
|
||||
|
/// </summary>
|
||||
|
public ClaimsPrincipal? Principal { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the token types that are considered valid.
|
||||
|
/// </summary>
|
||||
|
public HashSet<string> ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,130 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.IO; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.DataProtection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes; |
||||
|
using static OpenIddict.Validation.OpenIddictValidationEvents; |
||||
|
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; |
||||
|
using static OpenIddict.Validation.OpenIddictValidationHandlers.Protection; |
||||
|
using Schemes = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes.Schemes; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Validation.DataProtection |
||||
|
{ |
||||
|
public static partial class OpenIddictValidationDataProtectionHandlers |
||||
|
{ |
||||
|
public static class Protection |
||||
|
{ |
||||
|
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
||||
|
/* |
||||
|
* Token validation: |
||||
|
*/ |
||||
|
ValidateDataProtectionToken.Descriptor); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of validating tokens generated using Data Protection.
|
||||
|
/// </summary>
|
||||
|
public class ValidateDataProtectionToken : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly IOptionsMonitor<OpenIddictValidationDataProtectionOptions> _options; |
||||
|
|
||||
|
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictValidationDataProtectionOptions> options) |
||||
|
=> _options = options; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.UseSingletonHandler<ValidateDataProtectionToken>() |
||||
|
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
// If a principal was already attached, don't overwrite it.
|
||||
|
if (context.Principal is not null) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
|
||||
|
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
|
||||
|
if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal)) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// Note: unlike the equivalent handler in the server stack, the logic used here is
|
||||
|
// simpler as only access tokens are currently supported by the validation stack.
|
||||
|
var principal = context.ValidTokenTypes.Count is 0 || context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) ? |
||||
|
ValidateToken(context.Token, TokenTypeHints.AccessToken) : |
||||
|
null; |
||||
|
|
||||
|
if (principal is null) |
||||
|
{ |
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2004), |
||||
|
uri: SR.FormatID8000(SR.ID2004)); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
context.Principal = principal; |
||||
|
|
||||
|
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims); |
||||
|
|
||||
|
return default; |
||||
|
|
||||
|
ClaimsPrincipal? ValidateToken(string token, string type) |
||||
|
{ |
||||
|
// 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.
|
||||
|
TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) |
||||
|
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, |
||||
|
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, |
||||
|
|
||||
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) |
||||
|
}); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(token))); |
||||
|
using var reader = new BinaryReader(buffer); |
||||
|
|
||||
|
// 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)?.SetTokenType(type); |
||||
|
} |
||||
|
|
||||
|
catch (Exception exception) |
||||
|
{ |
||||
|
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), token); |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Owin.Security; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Validation.Owin |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public class OpenIddictValidationOwinProperties : AuthenticationProperties |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public OpenIddictValidationOwinProperties() |
||||
|
: this(items: null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public OpenIddictValidationOwinProperties(IDictionary<string, string?>? items) |
||||
|
: this(items, parameters: null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OpenIddictValidationOwinProperties"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="items">State values dictionary to use.</param>
|
||||
|
/// <param name="parameters">Parameters dictionary to use.</param>
|
||||
|
public OpenIddictValidationOwinProperties( |
||||
|
IDictionary<string, string?>? items, |
||||
|
IDictionary<string, object?>? parameters) |
||||
|
: base(items) |
||||
|
=> Parameters = parameters ?? new Dictionary<string, object?>(StringComparer.Ordinal); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the collection of parameters passed to the authentication handler.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Note: these properties are not intended for serialization or persistence,
|
||||
|
/// only for flowing data between call sites.
|
||||
|
/// </remarks>
|
||||
|
public IDictionary<string, object?> Parameters { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a parameter from the <see cref="Parameters"/> collection.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The parameter type.</typeparam>
|
||||
|
/// <param name="name">The parameter name.</param>
|
||||
|
/// <returns>The parameter value or a default value if the property is not set.</returns>
|
||||
|
public T? GetParameter<T>(string name) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(name)) |
||||
|
{ |
||||
|
throw new ArgumentException(SR.ID0190, nameof(name)); |
||||
|
} |
||||
|
|
||||
|
return Parameters.TryGetValue(name, out var parameter) && parameter is T value ? value : default; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets a parameter value in the <see cref="Parameters"/> collection.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The parameter type.</typeparam>
|
||||
|
/// <param name="name">The parameter key.</param>
|
||||
|
/// <param name="value">The value to set.</param>
|
||||
|
public void SetParameter<T>(string name, T? value) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(name)) |
||||
|
{ |
||||
|
throw new ArgumentException(SR.ID0190, nameof(name)); |
||||
|
} |
||||
|
|
||||
|
if (value is null) |
||||
|
{ |
||||
|
Parameters.Remove(name); |
||||
|
} |
||||
|
|
||||
|
else |
||||
|
{ |
||||
|
Parameters[name] = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,16 +0,0 @@ |
|||||
/* |
|
||||
* 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. |
|
||||
*/ |
|
||||
|
|
||||
namespace OpenIddict.Validation |
|
||||
{ |
|
||||
public static class OpenIddictValidationConstants |
|
||||
{ |
|
||||
public static class Properties |
|
||||
{ |
|
||||
public const string ReferenceTokenIdentifier = ".reference_token_identifier"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,72 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Claims; |
||||
|
using Microsoft.IdentityModel.JsonWebTokens; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using OpenIddict.Abstractions; |
||||
|
|
||||
|
namespace OpenIddict.Validation |
||||
|
{ |
||||
|
public static partial class OpenIddictValidationEvents |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an event called when validating a token.
|
||||
|
/// </summary>
|
||||
|
public class ValidateTokenContext : BaseValidatingContext |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="ValidateTokenContext"/> class.
|
||||
|
/// </summary>
|
||||
|
public ValidateTokenContext(OpenIddictValidationTransaction transaction) |
||||
|
: base(transaction) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the request, or <c>null</c> if it is not available.
|
||||
|
/// </summary>
|
||||
|
public OpenIddictRequest? Request |
||||
|
{ |
||||
|
get => Transaction.Request; |
||||
|
set => Transaction.Request = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security token handler used to validate the token.
|
||||
|
/// </summary>
|
||||
|
public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the validation parameters used to verify the authenticity of tokens.
|
||||
|
/// </summary>
|
||||
|
public TokenValidationParameters TokenValidationParameters { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the token to validate.
|
||||
|
/// </summary>
|
||||
|
public string Token { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the token entry identifier associated with the token, if applicable.
|
||||
|
/// </summary>
|
||||
|
public string? TokenId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security principal resolved from the token.
|
||||
|
/// </summary>
|
||||
|
public ClaimsPrincipal? Principal { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the token types that are considered valid. If no value is
|
||||
|
/// explicitly specified, all supported tokens are considered valid.
|
||||
|
/// </summary>
|
||||
|
public HashSet<string> ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,864 @@ |
|||||
|
/* |
||||
|
* 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; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Diagnostics; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
using static OpenIddict.Validation.OpenIddictValidationEvents; |
||||
|
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Validation |
||||
|
{ |
||||
|
public static partial class OpenIddictValidationHandlers |
||||
|
{ |
||||
|
public static class Protection |
||||
|
{ |
||||
|
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
||||
|
/* |
||||
|
* Token validation: |
||||
|
*/ |
||||
|
ResolveTokenValidationParameters.Descriptor, |
||||
|
ValidateReferenceTokenIdentifier.Descriptor, |
||||
|
ValidateIdentityModelToken.Descriptor, |
||||
|
IntrospectToken.Descriptor, |
||||
|
NormalizeScopeClaims.Descriptor, |
||||
|
MapInternalClaims.Descriptor, |
||||
|
RestoreReferenceTokenProperties.Descriptor, |
||||
|
ValidatePrincipal.Descriptor, |
||||
|
ValidateExpirationDate.Descriptor, |
||||
|
ValidateAudience.Descriptor, |
||||
|
ValidateTokenEntry.Descriptor, |
||||
|
ValidateAuthorizationEntry.Descriptor); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of resolving the validation parameters used to validate tokens.
|
||||
|
/// </summary>
|
||||
|
public class ResolveTokenValidationParameters : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.UseSingletonHandler<ResolveTokenValidationParameters>() |
||||
|
.SetOrder(int.MinValue + 100_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? |
||||
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); |
||||
|
|
||||
|
// Clone the token validation parameters and set the issuer using the value found in the
|
||||
|
// OpenID Connect server configuration (that can be static or retrieved using discovery).
|
||||
|
var parameters = context.Options.TokenValidationParameters.Clone(); |
||||
|
parameters.ValidIssuer ??= configuration.Issuer ?? context.Issuer?.AbsoluteUri; |
||||
|
parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer); |
||||
|
|
||||
|
// Combine the signing keys registered statically in the token validation parameters
|
||||
|
// with the signing keys resolved from the OpenID Connect server configuration.
|
||||
|
parameters.IssuerSigningKeys = |
||||
|
parameters.IssuerSigningKeys?.Concat(configuration.SigningKeys) ?? configuration.SigningKeys; |
||||
|
|
||||
|
parameters.ValidTypes = context.ValidTokenTypes.Count switch |
||||
|
{ |
||||
|
// If no specific token type is expected, accept all token types at this stage.
|
||||
|
// Additional filtering can be made based on the resolved/actual token type.
|
||||
|
0 => null, |
||||
|
|
||||
|
// Otherwise, map the token types to their JWT public or internal representation.
|
||||
|
_ => context.ValidTokenTypes.SelectMany(type => type switch |
||||
|
{ |
||||
|
// For access tokens, both "at+jwt" and "application/at+jwt" are valid.
|
||||
|
TokenTypeHints.AccessToken => new[] |
||||
|
{ |
||||
|
JsonWebTokenTypes.AccessToken, |
||||
|
JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken |
||||
|
}, |
||||
|
|
||||
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) |
||||
|
}) |
||||
|
}; |
||||
|
|
||||
|
context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; |
||||
|
context.TokenValidationParameters = parameters; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of validating reference token identifiers.
|
||||
|
/// Note: this handler is not used when the degraded mode is enabled.
|
||||
|
/// </summary>
|
||||
|
public class ValidateReferenceTokenIdentifier : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly IOpenIddictTokenManager _tokenManager; |
||||
|
|
||||
|
public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); |
||||
|
|
||||
|
public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager) |
||||
|
=> _tokenManager = tokenManager; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.AddFilter<RequireTokenEntryValidationEnabled>() |
||||
|
.UseScopedHandler<ValidateReferenceTokenIdentifier>() |
||||
|
.SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
// Reference tokens are base64url-encoded payloads of exactly 256 bits (generated using a
|
||||
|
// crypto-secure RNG). If the token length differs, the token cannot be a reference token.
|
||||
|
if (context.Token.Length != 43) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// If the reference token cannot be found, don't return an error to allow another handler to validate it.
|
||||
|
var token = await _tokenManager.FindByReferenceIdAsync(context.Token); |
||||
|
if (token is null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// If the type associated with the token entry doesn't match one of the expected types, return an error.
|
||||
|
if (context.ValidTokenTypes.Count > 0 && |
||||
|
!await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray())) |
||||
|
{ |
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2004), |
||||
|
uri: SR.FormatID8000(SR.ID2004)); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var payload = await _tokenManager.GetPayloadAsync(token); |
||||
|
if (string.IsNullOrEmpty(payload)) |
||||
|
{ |
||||
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0026)); |
||||
|
} |
||||
|
|
||||
|
// Replace the token parameter by the payload resolved from the token entry
|
||||
|
// and store the identifier of the reference token so it can be later
|
||||
|
// used to restore the properties associated with the token.
|
||||
|
context.Token = payload; |
||||
|
context.TokenId = await _tokenManager.GetIdAsync(token); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of validating tokens generated using IdentityModel.
|
||||
|
/// </summary>
|
||||
|
public class ValidateIdentityModelToken : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.UseSingletonHandler<ValidateIdentityModelToken>() |
||||
|
.SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
// If a principal was already attached, don't overwrite it.
|
||||
|
if (context.Principal is not null) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// If the token cannot be read, don't return an error to allow another handler to validate it.
|
||||
|
if (!context.SecurityTokenHandler.CanReadToken(context.Token)) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters); |
||||
|
if (!result.IsValid) |
||||
|
{ |
||||
|
// If validation failed because of an unrecognized key identifier, inform the configuration manager
|
||||
|
// that the configuration MAY have be refreshed by sending a new discovery request to the server.
|
||||
|
if (result.Exception is SecurityTokenSignatureKeyNotFoundException) |
||||
|
{ |
||||
|
context.Options.ConfigurationManager.RequestRefresh(); |
||||
|
} |
||||
|
|
||||
|
context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: result.Exception switch |
||||
|
{ |
||||
|
SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088), |
||||
|
SecurityTokenInvalidTypeException => SR.GetResourceString(SR.ID2089), |
||||
|
SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090), |
||||
|
SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091), |
||||
|
|
||||
|
_ => SR.GetResourceString(SR.ID2004) |
||||
|
}, |
||||
|
uri: result.Exception switch |
||||
|
{ |
||||
|
SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088), |
||||
|
SecurityTokenInvalidTypeException => SR.FormatID8000(SR.ID2089), |
||||
|
SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090), |
||||
|
SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091), |
||||
|
|
||||
|
_ => SR.FormatID8000(SR.ID2004) |
||||
|
}); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// Attach the principal extracted from the token to the parent event context and store
|
||||
|
// the token type (resolved from "typ" or "token_usage") as a special private claim.
|
||||
|
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch |
||||
|
{ |
||||
|
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), |
||||
|
|
||||
|
// Both at+jwt and application/at+jwt are supported for access tokens.
|
||||
|
JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken |
||||
|
=> TokenTypeHints.AccessToken, |
||||
|
|
||||
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) |
||||
|
}); |
||||
|
|
||||
|
context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of validating the tokens using OAuth 2.0 introspection.
|
||||
|
/// </summary>
|
||||
|
public class IntrospectToken : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly OpenIddictValidationService _service; |
||||
|
|
||||
|
public IntrospectToken(OpenIddictValidationService service) |
||||
|
=> _service = service; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireIntrospectionValidation>() |
||||
|
.UseSingletonHandler<IntrospectToken>() |
||||
|
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
// If a principal was already attached, don't overwrite it.
|
||||
|
if (context.Principal is not null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010)); |
||||
|
|
||||
|
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? |
||||
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); |
||||
|
|
||||
|
if (string.IsNullOrEmpty(configuration.IntrospectionEndpoint) || |
||||
|
!Uri.TryCreate(configuration.IntrospectionEndpoint, UriKind.Absolute, out Uri? address) || |
||||
|
!address.IsWellFormedOriginalString()) |
||||
|
{ |
||||
|
context.Reject( |
||||
|
error: Errors.ServerError, |
||||
|
description: SR.GetResourceString(SR.ID2092), |
||||
|
uri: SR.FormatID8000(SR.ID2092)); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
ClaimsPrincipal principal; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
principal = await _service.IntrospectTokenAsync(address, context.Token, context.ValidTokenTypes.Count switch |
||||
|
{ |
||||
|
// Infer the token type hint sent to the authorization server to help speed up
|
||||
|
// the token resolution lookup. If multiple types are accepted, no hint is sent.
|
||||
|
1 => context.ValidTokenTypes.ElementAt(0), |
||||
|
_ => null |
||||
|
}) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0141)); |
||||
|
} |
||||
|
|
||||
|
catch (Exception exception) |
||||
|
{ |
||||
|
context.Logger.LogDebug(exception, SR.GetResourceString(SR.ID6155)); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2004), |
||||
|
uri: SR.FormatID8000(SR.ID2004)); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// OpenIddict-based authorization servers always return the actual token type using
|
||||
|
// the special "token_usage" claim, that helps resource servers determine whether the
|
||||
|
// introspected token is one of the expected types and prevents token substitution attacks.
|
||||
|
//
|
||||
|
// If a "token_usage" claim can be extracted from the principal, use it to determine
|
||||
|
// whether the token details returned by the authorization server correspond to a
|
||||
|
// token whose type is considered acceptable based on the valid types collection.
|
||||
|
//
|
||||
|
// If the valid types collection is empty, all types of tokens are considered valid.
|
||||
|
var usage = principal.GetClaim(Claims.TokenUsage); |
||||
|
if (!string.IsNullOrEmpty(usage) && context.ValidTokenTypes.Count > 0 && |
||||
|
!context.ValidTokenTypes.Contains(usage)) |
||||
|
{ |
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2110), |
||||
|
uri: SR.FormatID8000(SR.ID2110)); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Note: at this point, the "token_usage" claim value is guaranteed to correspond
|
||||
|
// to a known value as it is checked when validating the introspection response.
|
||||
|
//
|
||||
|
// If no value could be resolved, the token is assumed to be an access token.
|
||||
|
context.Principal = principal.SetTokenType(usage ?? TokenTypeHints.AccessToken); |
||||
|
|
||||
|
context.Logger.LogTrace(SR.GetResourceString(SR.ID6154), context.Token, context.Principal.Claims); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of normalizing the scope claims stored in the tokens.
|
||||
|
/// </summary>
|
||||
|
public class NormalizeScopeClaims : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.UseSingletonHandler<NormalizeScopeClaims>() |
||||
|
.SetOrder(IntrospectToken.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
if (context.Principal is null) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
|
||||
|
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
|
||||
|
// is formatted as a unique space-separated string containing all the granted scopes.
|
||||
|
// 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.
|
||||
|
// 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.
|
||||
|
var scopes = context.Principal.GetClaims(Claims.Scope); |
||||
|
if (scopes.Length > 1) |
||||
|
{ |
||||
|
context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes)); |
||||
|
} |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of mapping internal claims used by OpenIddict.
|
||||
|
/// </summary>
|
||||
|
public class MapInternalClaims : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.UseSingletonHandler<MapInternalClaims>() |
||||
|
.SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
if (context.Principal is null) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// To reduce the size of tokens, some of the private claims used by OpenIddict
|
||||
|
// are mapped to their standard equivalent before being removed from the token.
|
||||
|
// This handler is responsible of adding back the private claims to the principal
|
||||
|
// when receiving the token (e.g "oi_prst" is resolved from the "scope" claim).
|
||||
|
|
||||
|
// In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt".
|
||||
|
// If the claim doesn't exist, try to infer it from the standard "iat" JWT claim.
|
||||
|
if (!context.Principal.HasClaim(Claims.Private.CreationDate)) |
||||
|
{ |
||||
|
var date = context.Principal.GetClaim(Claims.IssuedAt); |
||||
|
if (!string.IsNullOrEmpty(date) && |
||||
|
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) |
||||
|
{ |
||||
|
context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt".
|
||||
|
// If the claim doesn't exist, try to infer it from the standard "exp" JWT claim.
|
||||
|
if (!context.Principal.HasClaim(Claims.Private.ExpirationDate)) |
||||
|
{ |
||||
|
var date = context.Principal.GetClaim(Claims.ExpiresAt); |
||||
|
if (!string.IsNullOrEmpty(date) && |
||||
|
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) |
||||
|
{ |
||||
|
context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud".
|
||||
|
// If no such claim exists, try to infer them from the standard "aud" JWT claims.
|
||||
|
if (!context.Principal.HasClaim(Claims.Private.Audience)) |
||||
|
{ |
||||
|
var audiences = context.Principal.GetClaims(Claims.Audience); |
||||
|
if (audiences.Any()) |
||||
|
{ |
||||
|
context.Principal.SetAudiences(audiences); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst".
|
||||
|
// If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims.
|
||||
|
//
|
||||
|
// Note: in previous OpenIddict versions, the presenters were represented in JWT tokens
|
||||
|
// using the "azp" claim (defined by OpenID Connect), for which a single value could be
|
||||
|
// specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x
|
||||
|
// can still be read with OpenIddict 3.0, the presenter is automatically inferred from
|
||||
|
// the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal.
|
||||
|
if (!context.Principal.HasClaim(Claims.Private.Presenter)) |
||||
|
{ |
||||
|
var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ?? |
||||
|
context.Principal.GetClaim(Claims.ClientId); |
||||
|
|
||||
|
if (!string.IsNullOrEmpty(presenter)) |
||||
|
{ |
||||
|
context.Principal.SetPresenters(presenter); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp".
|
||||
|
// If no such claim exists, try to infer them from the standard "scope" JWT claim,
|
||||
|
// which is guaranteed to be a unique space-separated claim containing all the values.
|
||||
|
if (!context.Principal.HasClaim(Claims.Private.Scope)) |
||||
|
{ |
||||
|
var scope = context.Principal.GetClaim(Claims.Scope); |
||||
|
if (!string.IsNullOrEmpty(scope)) |
||||
|
{ |
||||
|
context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of restoring the properties associated with a reference token entry.
|
||||
|
/// Note: this handler is not used when the degraded mode is enabled.
|
||||
|
/// </summary>
|
||||
|
public class RestoreReferenceTokenProperties : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly IOpenIddictTokenManager _tokenManager; |
||||
|
|
||||
|
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); |
||||
|
|
||||
|
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) |
||||
|
=> _tokenManager = tokenManager; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.AddFilter<RequireTokenEntryValidationEnabled>() |
||||
|
.UseScopedHandler<RestoreReferenceTokenProperties>() |
||||
|
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var token = await _tokenManager.FindByIdAsync(context.TokenId); |
||||
|
if (token is null) |
||||
|
{ |
||||
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); |
||||
|
} |
||||
|
|
||||
|
// Restore the creation/expiration dates/identifiers from the token entry metadata.
|
||||
|
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) |
||||
|
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) |
||||
|
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) |
||||
|
.SetTokenId(await _tokenManager.GetIdAsync(token)) |
||||
|
.SetTokenType(await _tokenManager.GetTypeAsync(token)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved.
|
||||
|
/// </summary>
|
||||
|
public class ValidatePrincipal : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.UseSingletonHandler<ValidatePrincipal>() |
||||
|
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
if (context.Principal is null) |
||||
|
{ |
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2004), |
||||
|
uri: SR.FormatID8000(SR.ID2004)); |
||||
|
|
||||
|
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 (context.ValidTokenTypes.Count > 0) |
||||
|
{ |
||||
|
var type = context.Principal.GetTokenType(); |
||||
|
if (string.IsNullOrEmpty(type)) |
||||
|
{ |
||||
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); |
||||
|
} |
||||
|
|
||||
|
if (!context.ValidTokenTypes.Contains(type)) |
||||
|
{ |
||||
|
throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of rejecting authentication demands containing expired access tokens.
|
||||
|
/// </summary>
|
||||
|
public class ValidateExpirationDate : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.UseSingletonHandler<ValidateExpirationDate>() |
||||
|
.SetOrder(ValidatePrincipal.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); |
||||
|
|
||||
|
var date = context.Principal.GetExpirationDate(); |
||||
|
if (date.HasValue && date.Value < DateTimeOffset.UtcNow) |
||||
|
{ |
||||
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6156)); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2019), |
||||
|
uri: SR.FormatID8000(SR.ID2019)); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of rejecting authentication demands containing
|
||||
|
/// access tokens that were issued to be used by another audience/resource server.
|
||||
|
/// </summary>
|
||||
|
public class ValidateAudience : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.UseSingletonHandler<ValidateAudience>() |
||||
|
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); |
||||
|
|
||||
|
// If no explicit audience has been configured,
|
||||
|
// skip the default audience validation.
|
||||
|
if (context.Options.Audiences.Count == 0) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// If the access token doesn't have any audience attached, return an error.
|
||||
|
var audiences = context.Principal.GetAudiences(); |
||||
|
if (audiences.IsDefaultOrEmpty) |
||||
|
{ |
||||
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6157)); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2093), |
||||
|
uri: SR.FormatID8000(SR.ID2093)); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
// If the access token doesn't include any registered audience, return an error.
|
||||
|
if (!audiences.Intersect(context.Options.Audiences, StringComparer.Ordinal).Any()) |
||||
|
{ |
||||
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6158)); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2094), |
||||
|
uri: SR.FormatID8000(SR.ID2094)); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of authentication demands a token whose
|
||||
|
/// associated token entry is no longer valid (e.g was revoked).
|
||||
|
/// Note: this handler is not used when the degraded mode is enabled.
|
||||
|
/// </summary>
|
||||
|
public class ValidateTokenEntry : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly IOpenIddictTokenManager _tokenManager; |
||||
|
|
||||
|
public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); |
||||
|
|
||||
|
public ValidateTokenEntry(IOpenIddictTokenManager tokenManager) |
||||
|
=> _tokenManager = tokenManager; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.AddFilter<RequireTokenEntryValidationEnabled>() |
||||
|
.UseScopedHandler<ValidateTokenEntry>() |
||||
|
.SetOrder(ValidateAudience.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); |
||||
|
|
||||
|
var identifier = context.Principal.GetTokenId(); |
||||
|
if (string.IsNullOrEmpty(identifier)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var token = await _tokenManager.FindByIdAsync(identifier); |
||||
|
if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid)) |
||||
|
{ |
||||
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2019), |
||||
|
uri: SR.FormatID8000(SR.ID2019)); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Restore the creation/expiration dates/identifiers from the token entry metadata.
|
||||
|
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) |
||||
|
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) |
||||
|
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) |
||||
|
.SetTokenId(await _tokenManager.GetIdAsync(token)) |
||||
|
.SetTokenType(await _tokenManager.GetTypeAsync(token)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible of authentication demands a token whose
|
||||
|
/// associated authorization entry is no longer valid (e.g was revoked).
|
||||
|
/// Note: this handler is not used when the degraded mode is enabled.
|
||||
|
/// </summary>
|
||||
|
public class ValidateAuthorizationEntry : IOpenIddictValidationHandler<ValidateTokenContext> |
||||
|
{ |
||||
|
private readonly IOpenIddictAuthorizationManager _authorizationManager; |
||||
|
|
||||
|
public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0142)); |
||||
|
|
||||
|
public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager) |
||||
|
=> _authorizationManager = authorizationManager; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() |
||||
|
.AddFilter<RequireLocalValidation>() |
||||
|
.AddFilter<RequireAuthorizationEntryValidationEnabled>() |
||||
|
.UseScopedHandler<ValidateAuthorizationEntry>() |
||||
|
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) |
||||
|
.SetType(OpenIddictValidationHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async ValueTask HandleAsync(ValidateTokenContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); |
||||
|
|
||||
|
var identifier = context.Principal.GetAuthorizationId(); |
||||
|
if (string.IsNullOrEmpty(identifier)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var authorization = await _authorizationManager.FindByIdAsync(identifier); |
||||
|
if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) |
||||
|
{ |
||||
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); |
||||
|
|
||||
|
context.Reject( |
||||
|
error: Errors.InvalidToken, |
||||
|
description: SR.GetResourceString(SR.ID2023), |
||||
|
uri: SR.FormatID8000(SR.ID2023)); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue