diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
index 6cbb1925..eee68c57 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
@@ -365,14 +365,6 @@ namespace OpenIddict.Abstractions
///
ValueTask PruneAsync(CancellationToken cancellationToken = default);
- ///
- /// Revokes an authorization.
- ///
- /// The authorization to revoke.
- /// The that can be used to abort the operation.
- /// A that can be used to monitor the asynchronous operation.
- ValueTask RevokeAsync([NotNull] object authorization, CancellationToken cancellationToken = default);
-
///
/// Sets the application identifier associated with an authorization.
///
@@ -384,6 +376,14 @@ namespace OpenIddict.Abstractions
///
ValueTask SetApplicationIdAsync([NotNull] object authorization, [CanBeNull] string identifier, CancellationToken cancellationToken = default);
+ ///
+ /// Tries to revoke an authorization.
+ ///
+ /// The authorization to revoke.
+ /// The that can be used to abort the operation.
+ /// true if the authorization was successfully revoked, false otherwise.
+ ValueTask TryRevokeAsync([NotNull] object authorization, CancellationToken cancellationToken = default);
+
///
/// Updates an existing authorization.
///
diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
index f9e9868d..5978f846 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
@@ -71,17 +71,6 @@ namespace OpenIddict.Abstractions
///
ValueTask DeleteAsync([NotNull] object token, CancellationToken cancellationToken = default);
- ///
- /// Extends the specified token by replacing its expiration date.
- ///
- /// The token.
- /// The date on which the token will no longer be considered valid.
- /// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- ValueTask ExtendAsync([NotNull] object token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken = default);
-
///
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
@@ -394,22 +383,6 @@ namespace OpenIddict.Abstractions
///
ValueTask PruneAsync(CancellationToken cancellationToken = default);
- ///
- /// Redeems a token.
- ///
- /// The token to redeem.
- /// The that can be used to abort the operation.
- /// A that can be used to monitor the asynchronous operation.
- ValueTask RedeemAsync([NotNull] object token, CancellationToken cancellationToken = default);
-
- ///
- /// Revokes a token.
- ///
- /// The token to revoke.
- /// The that can be used to abort the operation.
- /// A that can be used to monitor the asynchronous operation.
- ValueTask RevokeAsync([NotNull] object token, CancellationToken cancellationToken = default);
-
///
/// Sets the application identifier associated with a token.
///
@@ -432,6 +405,31 @@ namespace OpenIddict.Abstractions
///
ValueTask SetAuthorizationIdAsync([NotNull] object token, [CanBeNull] string identifier, CancellationToken cancellationToken = default);
+ ///
+ /// Tries to extend the specified token by replacing its expiration date.
+ ///
+ /// The token.
+ /// The date on which the token will no longer be considered valid.
+ /// The that can be used to abort the operation.
+ /// true if the token was successfully extended, false otherwise.
+ ValueTask TryExtendAsync([NotNull] object token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken = default);
+
+ ///
+ /// Tries to redeem a token.
+ ///
+ /// The token to redeem.
+ /// The that can be used to abort the operation.
+ /// true if the token was successfully redemeed, false otherwise.
+ ValueTask TryRedeemAsync([NotNull] object token, CancellationToken cancellationToken = default);
+
+ ///
+ /// Tries to revoke a token.
+ ///
+ /// The token to revoke.
+ /// The that can be used to abort the operation.
+ /// true if the token was successfully revoked, false otherwise.
+ ValueTask TryRevokeAsync([NotNull] object token, CancellationToken cancellationToken = default);
+
///
/// Updates an existing token.
///
diff --git a/src/OpenIddict.Abstractions/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs
index 36f48b59..9a07bf05 100644
--- a/src/OpenIddict.Abstractions/OpenIddictConstants.cs
+++ b/src/OpenIddict.Abstractions/OpenIddictConstants.cs
@@ -95,9 +95,11 @@ namespace OpenIddict.Abstractions
public const string CodeChallengeMethod = "oi_cd_chlg_meth";
public const string IdentityTokenLifetime = "oi_idt_lft";
public const string Nonce = "oi_nce";
+ public const string Presenters = "oi_prst";
public const string RedirectUri = "oi_reduri";
public const string RefreshTokenLifetime = "oi_reft_lft";
- public const string Resource = "oi_rsrc";
+ public const string Resources = "oi_rsrc";
+ public const string Scopes = "oi_scp";
public const string TokenId = "oi_tkn_id";
public const string TokenUsage = "oi_tkn_use";
}
diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
index fa21789e..321bf763 100644
--- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
+++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
@@ -1095,7 +1095,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.AuthorizedParty));
+ return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.Private.Presenters));
}
///
@@ -1110,7 +1110,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.Private.Resource));
+ return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.Private.Resources));
}
///
@@ -1125,16 +1125,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- // Note: scopes are deliberately formatted as a single space-separated
- // string to respect the usual representation of the standard scope claim.
- // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
- var value = principal.GetClaim(Claims.Scope);
- if (string.IsNullOrEmpty(value))
- {
- return ImmutableHashSet.Create(StringComparer.Ordinal);
- }
-
- return ImmutableHashSet.CreateRange(StringComparer.Ordinal, GetValues(value, Separators.Space));
+ return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.Private.Scopes));
}
///
@@ -1275,21 +1266,6 @@ namespace OpenIddict.Abstractions
return principal.GetClaim(Claims.Private.TokenId);
}
- ///
- /// Gets the public token identifier associated with the claims principal.
- ///
- /// The claims principal.
- /// The unique identifier or null if the claim cannot be found.
- public static string GetPublicTokenId([NotNull] this ClaimsPrincipal principal)
- {
- if (principal == null)
- {
- throw new ArgumentNullException(nameof(principal));
- }
-
- return principal.GetClaim(Claims.JwtId);
- }
-
///
/// Gets the token usage associated with the claims principal.
///
@@ -1417,7 +1393,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return principal.FindAll(Claims.AuthorizedParty).Any();
+ return principal.FindAll(Claims.Private.Presenters).Any();
}
///
@@ -1453,7 +1429,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return principal.FindAll(Claims.Private.Resource).Any();
+ return principal.FindAll(Claims.Private.Resources).Any();
}
///
@@ -1489,7 +1465,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return principal.FindAll(Claims.Scope).Any();
+ return principal.FindAll(Claims.Private.Scopes).Any();
}
///
@@ -1530,7 +1506,8 @@ namespace OpenIddict.Abstractions
if (date.HasValue)
{
- var claim = new Claim(Claims.IssuedAt, date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64);
+ var value = date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture);
+ var claim = new Claim(Claims.IssuedAt, value, ClaimValueTypes.Integer64);
((ClaimsIdentity) principal.Identity).AddClaim(claim);
}
@@ -1554,7 +1531,8 @@ namespace OpenIddict.Abstractions
if (date.HasValue)
{
- var claim = new Claim(Claims.ExpiresAt, date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64);
+ var value = date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture);
+ var claim = new Claim(Claims.ExpiresAt, value, ClaimValueTypes.Integer64);
((ClaimsIdentity) principal.Identity).AddClaim(claim);
}
@@ -1609,7 +1587,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return principal.SetClaims(Claims.AuthorizedParty, presenters.Distinct(StringComparer.Ordinal));
+ return principal.SetClaims(Claims.Private.Presenters, presenters.Distinct(StringComparer.Ordinal));
}
///
@@ -1641,7 +1619,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- return principal.SetClaims(Claims.Private.Resource, resources.Distinct(StringComparer.Ordinal));
+ return principal.SetClaims(Claims.Private.Resources, resources.Distinct(StringComparer.Ordinal));
}
///
@@ -1672,15 +1650,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
- if (scopes == null)
- {
- return principal.RemoveClaims(Claims.Scope);
- }
-
- // Note: scopes are deliberately formatted as a single space-separated
- // string to respect the usual representation of the standard scope claim.
- // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
- return principal.SetClaim(Claims.Scope, string.Join(" ", scopes.Distinct(StringComparer.Ordinal)));
+ return principal.SetClaims(Claims.Private.Scopes, scopes.Distinct(StringComparer.Ordinal));
}
///
@@ -1792,22 +1762,6 @@ namespace OpenIddict.Abstractions
return principal.SetClaim(Claims.Private.TokenId, identifier);
}
- ///
- /// Sets the public token identifier associated with the claims principal.
- ///
- /// The claims principal.
- /// The unique identifier to store.
- /// The claims principal.
- public static ClaimsPrincipal SetPublicTokenId([NotNull] this ClaimsPrincipal principal, string identifier)
- {
- if (principal == null)
- {
- throw new ArgumentNullException(nameof(principal));
- }
-
- return principal.SetClaim(Claims.JwtId, identifier);
- }
-
private static IEnumerable GetValues(string source, char[] separators)
{
Debug.Assert(!string.IsNullOrEmpty(source), "The source string shouldn't be null or empty.");
diff --git a/src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj b/src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj
index d84c04d7..762352df 100644
--- a/src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj
+++ b/src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index 073d639e..1ce35c0b 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -18,6 +18,7 @@ using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
+using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.Core
{
@@ -912,45 +913,72 @@ namespace OpenIddict.Core
=> Store.PruneAsync(cancellationToken);
///
- /// Revokes an authorization.
+ /// Sets the application identifier associated with an authorization.
///
- /// The authorization to revoke.
+ /// The authorization.
+ /// The unique identifier associated with the client application.
/// The that can be used to abort the operation.
- /// A that can be used to monitor the asynchronous operation.
- public virtual async ValueTask RevokeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default)
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual async ValueTask SetApplicationIdAsync(
+ [NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken = default)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
- var status = await Store.GetStatusAsync(authorization, cancellationToken);
- if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
- {
- await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken);
- await UpdateAsync(authorization, cancellationToken);
- }
+ await Store.SetApplicationIdAsync(authorization, identifier, cancellationToken);
+ await UpdateAsync(authorization, cancellationToken);
}
///
- /// Sets the application identifier associated with an authorization.
+ /// Tries to revoke an authorization.
///
- /// The authorization.
- /// The unique identifier associated with the client application.
+ /// The authorization to revoke.
/// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public virtual async ValueTask SetApplicationIdAsync(
- [NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken = default)
+ /// true if the authorization was successfully revoked, false otherwise.
+ public virtual async ValueTask TryRevokeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
- await Store.SetApplicationIdAsync(authorization, identifier, cancellationToken);
- await UpdateAsync(authorization, cancellationToken);
+ var status = await Store.GetStatusAsync(authorization, cancellationToken);
+ if (string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken);
+
+ try
+ {
+ await UpdateAsync(authorization, cancellationToken);
+
+ Logger.LogInformation("The authorization '{Identifier}' was successfully revoked.",
+ await Store.GetIdAsync(authorization, cancellationToken));
+
+ return true;
+ }
+
+ catch (ConcurrencyException exception)
+ {
+ Logger.LogDebug(exception, "A concurrency exception occurred while trying to revoke the authorization '{Identifier}'.",
+ await Store.GetIdAsync(authorization, cancellationToken));
+
+ return false;
+ }
+
+ catch (Exception exception)
+ {
+ Logger.LogWarning(exception, "An exception occurred while trying to revoke the authorization '{Identifier}'.",
+ await Store.GetIdAsync(authorization, cancellationToken));
+
+ return false;
+ }
}
///
@@ -1169,12 +1197,12 @@ namespace OpenIddict.Core
ValueTask IOpenIddictAuthorizationManager.PruneAsync(CancellationToken cancellationToken)
=> PruneAsync(cancellationToken);
- ValueTask IOpenIddictAuthorizationManager.RevokeAsync(object authorization, CancellationToken cancellationToken)
- => RevokeAsync((TAuthorization) authorization, cancellationToken);
-
ValueTask IOpenIddictAuthorizationManager.SetApplicationIdAsync(object authorization, string identifier, CancellationToken cancellationToken)
=> SetApplicationIdAsync((TAuthorization) authorization, identifier, cancellationToken);
+ ValueTask IOpenIddictAuthorizationManager.TryRevokeAsync(object authorization, CancellationToken cancellationToken)
+ => TryRevokeAsync((TAuthorization) authorization, cancellationToken);
+
ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, CancellationToken cancellationToken)
=> UpdateAsync((TAuthorization) authorization, cancellationToken);
diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
index 02547677..74d5b331 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
@@ -18,6 +18,7 @@ using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
+using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.Core
{
@@ -194,27 +195,6 @@ namespace OpenIddict.Core
await Store.DeleteAsync(token, cancellationToken);
}
- ///
- /// Extends the specified token by replacing its expiration date.
- ///
- /// The token.
- /// The date on which the token will no longer be considered valid.
- /// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public virtual async ValueTask ExtendAsync([NotNull] TToken token,
- [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken = default)
- {
- if (token == null)
- {
- throw new ArgumentNullException(nameof(token));
- }
-
- await Store.SetExpirationDateAsync(token, date, cancellationToken);
- await UpdateAsync(token, cancellationToken);
- }
-
///
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
@@ -949,87 +929,201 @@ namespace OpenIddict.Core
=> Store.PruneAsync(cancellationToken);
///
- /// Redeems a token.
+ /// Sets the application identifier associated with a token.
///
- /// The token to redeem.
+ /// The token.
+ /// The unique identifier associated with the client application.
/// The that can be used to abort the operation.
- /// A that can be used to monitor the asynchronous operation.
- public virtual async ValueTask RedeemAsync([NotNull] TToken token, CancellationToken cancellationToken = default)
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual async ValueTask SetApplicationIdAsync([NotNull] TToken token,
+ [CanBeNull] string identifier, CancellationToken cancellationToken = default)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
- var status = await Store.GetStatusAsync(token, cancellationToken);
- if (!string.Equals(status, OpenIddictConstants.Statuses.Redeemed, StringComparison.OrdinalIgnoreCase))
+ await Store.SetApplicationIdAsync(token, identifier, cancellationToken);
+ await UpdateAsync(token, cancellationToken);
+ }
+
+ ///
+ /// Sets the authorization identifier associated with a token.
+ ///
+ /// The token.
+ /// The unique identifier associated with the authorization.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual async ValueTask SetAuthorizationIdAsync([NotNull] TToken token,
+ [CanBeNull] string identifier, CancellationToken cancellationToken = default)
+ {
+ if (token == null)
{
- await Store.SetStatusAsync(token, OpenIddictConstants.Statuses.Redeemed, cancellationToken);
- await UpdateAsync(token, cancellationToken);
+ throw new ArgumentNullException(nameof(token));
}
+
+ await Store.SetAuthorizationIdAsync(token, identifier, cancellationToken);
+ await UpdateAsync(token, cancellationToken);
}
///
- /// Revokes a token.
+ /// Tries to extend the specified token by replacing its expiration date.
///
- /// The token to revoke.
+ /// The token.
+ /// The date on which the token will no longer be considered valid.
/// The that can be used to abort the operation.
- /// A that can be used to monitor the asynchronous operation.
- public virtual async ValueTask RevokeAsync([NotNull] TToken token, CancellationToken cancellationToken = default)
+ /// true if the token was successfully extended, false otherwise.
+ public virtual async ValueTask TryExtendAsync([NotNull] TToken token,
+ [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken = default)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
- var status = await Store.GetStatusAsync(token, cancellationToken);
- if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
+ if (date == await Store.GetExpirationDateAsync(token, cancellationToken))
+ {
+ return true;
+ }
+
+ await Store.SetExpirationDateAsync(token, date, cancellationToken);
+
+ try
{
- await Store.SetStatusAsync(token, OpenIddictConstants.Statuses.Revoked, cancellationToken);
await UpdateAsync(token, cancellationToken);
+
+ if (date != null)
+ {
+ Logger.LogInformation("The expiration date of the refresh token '{Identifier}' was successfully updated: {Date}.",
+ await Store.GetIdAsync(token, cancellationToken), date);
+ }
+
+ else
+ {
+ Logger.LogInformation("The expiration date of the refresh token '{Identifier}' was successfully removed.",
+ await Store.GetIdAsync(token, cancellationToken));
+ }
+
+ return true;
+ }
+
+ catch (ConcurrencyException exception)
+ {
+ Logger.LogDebug(exception, "A concurrency exception occurred while trying to update the " +
+ "expiration date of the token '{Identifier}'.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return false;
+ }
+
+ catch (Exception exception)
+ {
+ Logger.LogWarning(exception, "An exception occurred while trying to update the " +
+ "expiration date of the token '{Identifier}'.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return false;
}
}
///
- /// Sets the application identifier associated with a token.
+ /// Tries to redeem a token.
///
- /// The token.
- /// The unique identifier associated with the client application.
+ /// The token to redeem.
/// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public virtual async ValueTask SetApplicationIdAsync([NotNull] TToken token,
- [CanBeNull] string identifier, CancellationToken cancellationToken = default)
+ /// true if the token was successfully redemeed, false otherwise.
+ public virtual async ValueTask TryRedeemAsync([NotNull] TToken token, CancellationToken cancellationToken = default)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
- await Store.SetApplicationIdAsync(token, identifier, cancellationToken);
- await UpdateAsync(token, cancellationToken);
+ var status = await Store.GetStatusAsync(token, cancellationToken);
+ if (string.Equals(status, OpenIddictConstants.Statuses.Redeemed, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ await Store.SetStatusAsync(token, OpenIddictConstants.Statuses.Redeemed, cancellationToken);
+
+ try
+ {
+ await UpdateAsync(token, cancellationToken);
+
+ Logger.LogInformation("The token '{Identifier}' was successfully marked as redeemed.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return true;
+ }
+
+ catch (ConcurrencyException exception)
+ {
+ Logger.LogDebug(exception, "A concurrency exception occurred while trying to redeem the token '{Identifier}'.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return false;
+ }
+
+ catch (Exception exception)
+ {
+ Logger.LogWarning(exception, "An exception occurred while trying to redeem the token '{Identifier}'.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return false;
+ }
}
///
- /// Sets the authorization identifier associated with a token.
+ /// Tries to revoke a token.
///
- /// The token.
- /// The unique identifier associated with the authorization.
+ /// The token to revoke.
/// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public virtual async ValueTask SetAuthorizationIdAsync([NotNull] TToken token,
- [CanBeNull] string identifier, CancellationToken cancellationToken = default)
+ /// true if the token was successfully revoked, false otherwise.
+ public virtual async ValueTask TryRevokeAsync([NotNull] TToken token, CancellationToken cancellationToken = default)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
- await Store.SetAuthorizationIdAsync(token, identifier, cancellationToken);
- await UpdateAsync(token, cancellationToken);
+ var status = await Store.GetStatusAsync(token, cancellationToken);
+ if (string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ await Store.SetStatusAsync(token, OpenIddictConstants.Statuses.Revoked, cancellationToken);
+
+ try
+ {
+ await UpdateAsync(token, cancellationToken);
+
+ Logger.LogInformation("The token '{Identifier}' was successfully revoked.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return true;
+ }
+
+ catch (ConcurrencyException exception)
+ {
+ Logger.LogDebug(exception, "A concurrency exception occurred while trying to revoke the token '{Identifier}'.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return false;
+ }
+
+ catch (Exception exception)
+ {
+ Logger.LogWarning(exception, "An exception occurred while trying to revoke the token '{Identifier}'.",
+ await Store.GetIdAsync(token, cancellationToken));
+
+ return false;
+ }
}
///
@@ -1201,9 +1295,6 @@ namespace OpenIddict.Core
ValueTask IOpenIddictTokenManager.DeleteAsync(object token, CancellationToken cancellationToken)
=> DeleteAsync((TToken) token, cancellationToken);
- ValueTask IOpenIddictTokenManager.ExtendAsync(object token, DateTimeOffset? date, CancellationToken cancellationToken)
- => ExtendAsync((TToken) token, date, cancellationToken);
-
IAsyncEnumerable
public IDictionary Claims { get; }
= new Dictionary(StringComparer.Ordinal);
-
- ///
- /// Gets or sets a boolean indicating whether
- /// the token was successfully revoked.
- ///
- public bool Revoked { get; set; }
}
///
diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Serialization.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Serialization.cs
deleted file mode 100644
index a4f3c1f7..00000000
--- a/src/OpenIddict.Server/OpenIddictServerEvents.Serialization.cs
+++ /dev/null
@@ -1,229 +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.Security.Claims;
-using JetBrains.Annotations;
-using Microsoft.IdentityModel.JsonWebTokens;
-using Microsoft.IdentityModel.Tokens;
-using static OpenIddict.Abstractions.OpenIddictConstants;
-
-namespace OpenIddict.Server
-{
- public static partial class OpenIddictServerEvents
- {
- ///
- /// Represents an abstract base class used for certain event contexts.
- ///
- public abstract class BaseSerializingContext : BaseContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public BaseSerializingContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- {
- }
-
- ///
- /// Gets or sets the security principal containing the claims to serialize.
- ///
- public ClaimsPrincipal Principal { get; set; }
-
- ///
- /// Gets or sets the encrypting credentials used to encrypt the token.
- ///
- public EncryptingCredentials EncryptingCredentials { get; set; }
-
- ///
- /// Gets or sets the signing credentials used to sign the token.
- ///
- public SigningCredentials SigningCredentials { get; set; }
-
- ///
- /// Gets or sets the security token handler used to serialize the token.
- ///
- public JsonWebTokenHandler SecurityTokenHandler { get; set; }
-
- ///
- /// Gets or sets the token returned to the client application.
- ///
- public string Token { get; set; }
-
- ///
- /// Gets or sets the token usage.
- ///
- public string TokenUsage { get; set; }
-
- ///
- /// Gets a boolean indicating whether the
- /// method was called.
- ///
- public bool IsHandled { get; private set; }
-
- ///
- /// Marks the serialization process as handled by the application code.
- ///
- public void HandleSerialization() => IsHandled = true;
- }
-
- ///
- /// Represents an abstract base class used for certain event contexts.
- ///
- public abstract class BaseDeserializingContext : BaseContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public BaseDeserializingContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- {
- }
-
- ///
- /// Gets or sets the security principal containing the deserialized claims.
- ///
- public ClaimsPrincipal Principal { get; set; }
-
- ///
- /// Gets or sets the validation parameters used to verify the authenticity of access tokens.
- /// Note: this property is only used when is not null.
- ///
- public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters();
-
- ///
- /// Gets or sets the security token handler used to
- /// deserialize the authentication ticket.
- ///
- public JsonWebTokenHandler SecurityTokenHandler { get; set; }
-
- ///
- /// Gets or sets the token used by the client application.
- ///
- public string Token { get; set; }
-
- ///
- /// Gets or sets the token usage.
- ///
- public string TokenUsage { get; set; }
-
- ///
- /// Gets a boolean indicating whether the
- /// method was called.
- ///
- public bool IsHandled { get; private set; }
-
- ///
- /// Marks the deserialization process as handled by the application code.
- ///
- public void HandleDeserialization() => IsHandled = true;
- }
-
- ///
- /// Represents an event called when serializing an access token.
- ///
- public class SerializeAccessTokenContext : BaseSerializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public SerializeAccessTokenContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.AccessToken;
- }
-
- ///
- /// Represents an event called when serializing an authorization code.
- ///
- public class SerializeAuthorizationCodeContext : BaseSerializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public SerializeAuthorizationCodeContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.AuthorizationCode;
- }
-
- ///
- /// Represents an event called when serializing an identity token.
- ///
- public class SerializeIdentityTokenContext : BaseSerializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public SerializeIdentityTokenContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.IdToken;
- }
-
- ///
- /// Represents an event called when serializing a refresh token.
- ///
- public class SerializeRefreshTokenContext : BaseSerializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public SerializeRefreshTokenContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.RefreshToken;
- }
-
- ///
- /// Represents an event called when deserializing an access token.
- ///
- public class DeserializeAccessTokenContext : BaseDeserializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public DeserializeAccessTokenContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.AccessToken;
- }
-
- ///
- /// Represents an event called when deserializing an authorization code.
- ///
- public class DeserializeAuthorizationCodeContext : BaseDeserializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public DeserializeAuthorizationCodeContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.AuthorizationCode;
- }
-
- ///
- /// Represents an event called when deserializing an identity token.
- ///
- public class DeserializeIdentityTokenContext : BaseDeserializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public DeserializeIdentityTokenContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.IdToken;
- }
-
- ///
- /// Represents an event called when deserializing a refresh token.
- ///
- public class DeserializeRefreshTokenContext : BaseDeserializingContext
- {
- ///
- /// Creates a new instance of the class.
- ///
- public DeserializeRefreshTokenContext([NotNull] OpenIddictServerTransaction transaction)
- : base(transaction)
- => TokenUsage = TokenUsages.RefreshToken;
- }
- }
-}
diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.cs b/src/OpenIddict.Server/OpenIddictServerEvents.cs
index 4b61cb79..4a5bda4a 100644
--- a/src/OpenIddict.Server/OpenIddictServerEvents.cs
+++ b/src/OpenIddict.Server/OpenIddictServerEvents.cs
@@ -371,6 +371,30 @@ namespace OpenIddict.Server
/// recommended, except when dealing with non-standard clients.
///
public bool IncludeRefreshToken { get; set; }
+
+ ///
+ /// Gets or sets the principal containing the claims that
+ /// will be used to create the access token, if applicable.
+ ///
+ public ClaimsPrincipal AccessTokenPrincipal { get; set; }
+
+ ///
+ /// Gets or sets the principal containing the claims that
+ /// will be used to create the authorization code, if applicable.
+ ///
+ public ClaimsPrincipal AuthorizationCodePrincipal { get; set; }
+
+ ///
+ /// Gets or sets the principal containing the claims that
+ /// will be used to create the identity token, if applicable.
+ ///
+ public ClaimsPrincipal IdentityTokenPrincipal { get; set; }
+
+ ///
+ /// Gets or sets the principal containing the claims that
+ /// will be used to create the refresh token, if applicable.
+ ///
+ public ClaimsPrincipal RefreshTokenPrincipal { get; set; }
}
///
diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs
index 62ebed15..f1d3663e 100644
--- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs
@@ -52,9 +52,15 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
index a45f7491..957d44fc 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
@@ -159,6 +159,38 @@ namespace OpenIddict.Server
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if reference tokens are enabled.
+ ///
+ public class RequireReferenceTokensDisabled : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new ValueTask(!context.Options.UseReferenceTokens);
+ }
+ }
+
+ ///
+ /// Represents a filter that excludes the associated handlers if reference tokens are disabled.
+ ///
+ public class RequireReferenceTokensEnabled : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new ValueTask(context.Options.UseReferenceTokens);
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if no refresh token is returned.
///
@@ -175,6 +207,38 @@ namespace OpenIddict.Server
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if rolling tokens were enabled.
+ ///
+ public class RequireRollingTokensDisabled : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new ValueTask(!context.Options.UseRollingTokens);
+ }
+ }
+
+ ///
+ /// Represents a filter that excludes the associated handlers if rolling tokens were not enabled.
+ ///
+ public class RequireRollingTokensEnabled : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new ValueTask(context.Options.UseRollingTokens);
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if scope permissions were disabled.
///
@@ -206,5 +270,37 @@ namespace OpenIddict.Server
return new ValueTask(!context.Options.DisableScopeValidation);
}
}
+
+ ///
+ /// Represents a filter that excludes the associated handlers if sliding expiration was disabled.
+ ///
+ public class RequireSlidingExpirationEnabled : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new ValueTask(context.Options.UseSlidingExpiration);
+ }
+ }
+
+ ///
+ /// Represents a filter that excludes the associated handlers if token storage was not enabled.
+ ///
+ public class RequireTokenStorageEnabled : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new ValueTask(!context.Options.DisableTokenStorage);
+ }
+ }
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
index b8178226..ac0498e7 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
@@ -1015,23 +1015,29 @@ namespace OpenIddict.Server
return;
}
- var notification = new DeserializeIdentityTokenContext(context.Transaction)
- {
- Token = context.Request.IdTokenHint
- };
-
+ var notification = new ProcessAuthenticationContext(context.Transaction);
await _provider.DispatchAsync(notification);
- if (notification.Principal == null)
+ if (notification.IsRequestHandled)
{
- context.Reject(
- error: Errors.InvalidRequest,
- description: "The specified 'id_token_hint' parameter is invalid or malformed.");
+ context.HandleRequest();
+ return;
+ }
+ else if (notification.IsRequestSkipped)
+ {
+ context.SkipRequest();
return;
}
- // Note: the expiration date associated with an identity token used as an id_token_hint is deliberately ignored.
+ else if (notification.IsRejected)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
// Attach the security principal extracted from the identity token to the
// validation context and store it as an environment property.
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
index 9b602216..fa313286 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
@@ -54,13 +54,11 @@ namespace OpenIddict.Server
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
ValidateScopePermissions.Descriptor,
- ValidateAuthorizationCode.Descriptor,
- ValidateRefreshToken.Descriptor,
+ ValidateToken.Descriptor,
ValidatePresenters.Descriptor,
ValidateRedirectUri.Descriptor,
ValidateCodeVerifier.Descriptor,
ValidateGrantedScopes.Descriptor,
- ValidateAuthorization.Descriptor,
/*
* Token request handling:
@@ -1186,13 +1184,14 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of rejecting token requests that specify an invalid authorization code.
+ /// Contains the logic responsible of rejecting token requests
+ /// that don't specify a valid authorization code or refresh token.
///
- public class ValidateAuthorizationCode : IOpenIddictServerHandler
+ public class ValidateToken : IOpenIddictServerHandler
{
private readonly IOpenIddictServerProvider _provider;
- public ValidateAuthorizationCode([NotNull] IOpenIddictServerProvider provider)
+ public ValidateToken([NotNull] IOpenIddictServerProvider provider)
=> _provider = provider;
///
@@ -1200,12 +1199,8 @@ namespace OpenIddict.Server
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseScopedHandler()
- // This handler is deliberately registered with a high order to ensure it runs
- // after custom handlers registered with the default order and prevent the token
- // endpoint from disclosing whether an authorization code or refresh token is
- // valid before the caller's identity can first be fully verified.
- .SetOrder(100_000)
+ .UseScopedHandler()
+ .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
.Build();
///
@@ -1222,116 +1217,40 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- if (!context.Request.IsAuthorizationCodeGrantType())
+ if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
{
return;
}
- var notification = new DeserializeAuthorizationCodeContext(context.Transaction)
- {
- Token = context.Request.Code
- };
-
+ var notification = new ProcessAuthenticationContext(context.Transaction);
await _provider.DispatchAsync(notification);
- if (notification.Principal == null)
- {
- context.Logger.LogError("The token request was rejected because the authorization code was invalid.");
-
- context.Reject(
- error: Errors.InvalidGrant,
- description: "The specified authorization code is invalid.");
-
- return;
- }
-
- var date = notification.Principal.GetExpirationDate();
- if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
- {
- context.Logger.LogError("The token request was rejected because the authorization code was expired.");
-
- context.Reject(
- error: Errors.InvalidGrant,
- description: "The specified authorization code is no longer valid.");
-
- return;
- }
-
- // Attach the principal extracted from the authorization code to the parent event context.
- context.Principal = notification.Principal;
- }
- }
-
- ///
- /// Contains the logic responsible of rejecting token requests that specify an invalid refresh token.
- ///
- public class ValidateRefreshToken : IOpenIddictServerHandler
- {
- private readonly IOpenIddictServerProvider _provider;
-
- public ValidateRefreshToken([NotNull] IOpenIddictServerProvider provider)
- => _provider = provider;
-
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseScopedHandler()
- .SetOrder(ValidateAuthorizationCode.Descriptor.Order + 1_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public async ValueTask HandleAsync([NotNull] ValidateTokenRequestContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (!context.Request.IsRefreshTokenGrantType())
+ if (notification.IsRequestHandled)
{
+ context.HandleRequest();
return;
}
- var notification = new DeserializeRefreshTokenContext(context.Transaction)
- {
- Token = context.Request.RefreshToken
- };
-
- await _provider.DispatchAsync(notification);
-
- if (notification.Principal == null)
+ else if (notification.IsRequestSkipped)
{
- context.Logger.LogError("The token request was rejected because the refresh token was invalid.");
-
- context.Reject(
- error: Errors.InvalidGrant,
- description: "The specified refresh token is invalid.");
-
+ context.SkipRequest();
return;
}
- var date = notification.Principal.GetExpirationDate();
- if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
+ else if (notification.IsRejected)
{
- context.Logger.LogError("The token request was rejected because the refresh token was expired.");
-
context.Reject(
- error: Errors.InvalidGrant,
- description: "The specified refresh token is no longer valid.");
-
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
return;
}
- // Attach the principal extracted from the refresh token to the parent event context.
+ // Attach the security principal extracted from the token to the
+ // validation context and store it as an environment property.
context.Principal = notification.Principal;
+ context.Transaction.Properties[Properties.AmbientPrincipal] = notification.Principal;
+ context.Transaction.Properties[Properties.OriginalPrincipal] = notification.Principal.Clone(_ => true);
}
}
@@ -1347,7 +1266,7 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000)
+ .SetOrder(ValidateToken.Descriptor.Order + 1_000)
.Build();
///
@@ -1688,67 +1607,6 @@ namespace OpenIddict.Server
}
}
- ///
- /// Contains the logic responsible of rejecting token requests that use an authorization code
- /// or refresh token whose associated authorization is no longer valid (e.g was revoked).
- /// Note: this handler is not used when the degraded mode is enabled.
- ///
- public class ValidateAuthorization : IOpenIddictServerHandler
- {
- private readonly IOpenIddictAuthorizationManager _authorizationManager;
-
- public ValidateAuthorization() => throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
- .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
- .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
- .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
- .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
- .ToString());
-
- public ValidateAuthorization([NotNull] IOpenIddictAuthorizationManager authorizationManager)
- => _authorizationManager = authorizationManager;
-
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .AddFilter()
- .AddFilter()
- .UseScopedHandler()
- .SetOrder(ValidateGrantedScopes.Descriptor.Order + 1_000)
- .Build();
-
- public async ValueTask HandleAsync([NotNull] ValidateTokenRequestContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- var identifier = context.Principal.GetInternalAuthorizationId();
- if (string.IsNullOrEmpty(identifier))
- {
- return;
- }
-
- var authorization = await _authorizationManager.FindByIdAsync(identifier);
- if (authorization == null || !await _authorizationManager.IsValidAsync(authorization))
- {
- context.Logger.LogError("The token '{Identifier}' was rejected because the associated " +
- "authorization was no longer valid.", context.Principal.GetPublicTokenId());
-
- context.Reject(
- error: Errors.InvalidGrant,
- description: context.Request.IsAuthorizationCodeGrantType() ?
- "The authorization associated with the authorization code is no longer valid." :
- "The authorization associated with the refresh token is no longer valid.");
-
- return;
- }
- }
- }
-
///
/// Contains the logic responsible of attaching the principal extracted
/// from the authorization code/refresh token to the event context.
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
index 79d7df38..48b5937f 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
@@ -48,7 +48,6 @@ namespace OpenIddict.Server
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
ValidateAuthorizedParty.Descriptor,
- ValidateAuthorization.Descriptor,
/*
* Introspection request handling:
@@ -656,7 +655,7 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of rejecting introspection requests that specify an invalid token.
+ /// Contains the logic responsible of rejecting introspection requests that don't specify a valid token.
///
public class ValidateToken : IOpenIddictServerHandler
{
@@ -671,11 +670,7 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.UseScopedHandler()
- // This handler is deliberately registered with a high order to ensure it runs
- // after custom handlers registered with the default order and prevent the token
- // endpoint from disclosing whether the introspected token is valid before
- // the caller's identity can first be fully verified by the other handlers.
- .SetOrder(100_000)
+ .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.Build();
///
@@ -692,117 +687,34 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- // Note: use the "token_type_hint" parameter specified by the client application
- // to try to determine the type of the token sent by the client application.
- // See https://tools.ietf.org/html/rfc7662#section-2.1 for more information.
- var principal = context.Request.TokenTypeHint switch
- {
- TokenTypeHints.AccessToken => await DeserializeAccessTokenAsync(),
- TokenTypeHints.AuthorizationCode => await DeserializeAuthorizationCodeAsync(),
- TokenTypeHints.IdToken => await DeserializeIdentityTokenAsync(),
- TokenTypeHints.RefreshToken => await DeserializeRefreshTokenAsync(),
-
- _ => null
- };
-
- // Note: if the introspected token can't be found using "token_type_hint",
- // the search must be extended to all supported token types.
- // See https://tools.ietf.org/html/rfc7662#section-2.1 for more information.
- // To avoid calling the same deserialization methods twice, an additional check
- // is made to exclude the corresponding call when a token_type_hint was specified.
- principal ??= context.Request.TokenTypeHint switch
- {
- TokenTypeHints.AccessToken => await DeserializeAuthorizationCodeAsync() ??
- await DeserializeIdentityTokenAsync() ??
- await DeserializeRefreshTokenAsync(),
-
- TokenTypeHints.AuthorizationCode => await DeserializeAccessTokenAsync() ??
- await DeserializeIdentityTokenAsync() ??
- await DeserializeRefreshTokenAsync(),
-
- TokenTypeHints.IdToken => await DeserializeAccessTokenAsync() ??
- await DeserializeAuthorizationCodeAsync() ??
- await DeserializeRefreshTokenAsync(),
-
- TokenTypeHints.RefreshToken => await DeserializeAccessTokenAsync() ??
- await DeserializeAuthorizationCodeAsync() ??
- await DeserializeIdentityTokenAsync(),
-
- _ => await DeserializeAccessTokenAsync() ??
- await DeserializeAuthorizationCodeAsync() ??
- await DeserializeIdentityTokenAsync() ??
- await DeserializeRefreshTokenAsync()
- };
+ var notification = new ProcessAuthenticationContext(context.Transaction);
+ await _provider.DispatchAsync(notification);
- if (principal == null)
+ if (notification.IsRequestHandled)
{
- context.Logger.LogError("The introspection request was rejected because the token was invalid.");
-
- context.Reject(
- error: Errors.InvalidToken,
- description: "The specified token is invalid.");
-
+ context.HandleRequest();
return;
}
- var date = principal.GetExpirationDate();
- if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
+ else if (notification.IsRequestSkipped)
{
- context.Logger.LogError("The introspection request was rejected because the token was expired.");
-
- context.Reject(
- error: Errors.InvalidToken,
- description: "The specified token is no longer valid.");
-
+ context.SkipRequest();
return;
}
- // Attach the principal extracted from the token to the parent event context.
- context.Principal = principal;
-
- async ValueTask DeserializeAccessTokenAsync()
- {
- var notification = new DeserializeAccessTokenContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
- }
-
- async ValueTask DeserializeAuthorizationCodeAsync()
- {
- var notification = new DeserializeAuthorizationCodeContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
- }
-
- async ValueTask DeserializeIdentityTokenAsync()
+ else if (notification.IsRejected)
{
- var notification = new DeserializeIdentityTokenContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
}
- async ValueTask DeserializeRefreshTokenAsync()
- {
- var notification = new DeserializeRefreshTokenContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
- }
+ // Attach the security principal extracted from the token to the
+ // validation context and store it as an environment property.
+ context.Principal = notification.Principal;
+ context.Transaction.Properties[Properties.AmbientPrincipal] = notification.Principal;
}
}
@@ -822,7 +734,7 @@ namespace OpenIddict.Server
// In this case, the returned claims are limited by AttachApplicationClaims to limit exposure.
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ValidateToken.Descriptor.Order + 1_000)
+ .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.Build();
///
@@ -919,65 +831,6 @@ namespace OpenIddict.Server
}
}
- ///
- /// Contains the logic responsible of rejecting introspection requests that use
- /// a token whose associated authorization is no longer valid (e.g was revoked).
- /// Note: this handler is not used when the degraded mode is enabled.
- ///
- public class ValidateAuthorization : IOpenIddictServerHandler
- {
- private readonly IOpenIddictAuthorizationManager _authorizationManager;
-
- public ValidateAuthorization() => throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
- .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
- .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
- .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
- .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
- .ToString());
-
- public ValidateAuthorization([NotNull] IOpenIddictAuthorizationManager authorizationManager)
- => _authorizationManager = authorizationManager;
-
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .AddFilter()
- .AddFilter()
- .UseScopedHandler()
- .SetOrder(ValidateAuthorizedParty.Descriptor.Order + 1_000)
- .Build();
-
- public async ValueTask HandleAsync([NotNull] ValidateIntrospectionRequestContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- var identifier = context.Principal.GetInternalAuthorizationId();
- if (string.IsNullOrEmpty(identifier))
- {
- return;
- }
-
- var authorization = await _authorizationManager.FindByIdAsync(identifier);
- if (authorization == null || !await _authorizationManager.IsValidAsync(authorization))
- {
- context.Logger.LogError("The token '{Identifier}' was rejected because the associated " +
- "authorization was no longer valid.", context.Principal.GetPublicTokenId());
-
- context.Reject(
- error: Errors.InvalidGrant,
- description: "The authorization associated with the token is no longer valid.");
-
- return;
- }
- }
- }
-
///
/// Contains the logic responsible of attaching the principal
/// extracted from the introspected token to the event context.
@@ -1044,7 +897,7 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- context.TokenId = context.Principal.GetPublicTokenId();
+ context.TokenId = context.Principal.GetClaim(Claims.JwtId);
context.TokenUsage = context.Principal.GetTokenUsage();
context.Subject = context.Principal.GetClaim(Claims.Subject);
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
index c7d68df1..682d8be4 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
@@ -48,6 +48,7 @@ namespace OpenIddict.Server
* Revocation request handling:
*/
AttachPrincipal.Descriptor,
+ RevokeToken.Descriptor,
/*
* Revocation response handling:
@@ -602,7 +603,7 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of rejecting revocation requests that specify an invalid token.
+ /// Contains the logic responsible of rejecting revocation requests that don't specify a valid token.
///
public class ValidateToken : IOpenIddictServerHandler
{
@@ -617,11 +618,7 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.UseScopedHandler()
- // This handler is deliberately registered with a high order to ensure it runs
- // after custom handlers registered with the default order and prevent the token
- // endpoint from disclosing whether the revoked token is valid before
- // the caller's identity can first be fully verified by the other handlers.
- .SetOrder(100_000)
+ .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.Build();
///
@@ -638,117 +635,34 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- // Note: use the "token_type_hint" parameter specified by the client application
- // to try to determine the type of the token sent by the client application.
- // See https://tools.ietf.org/html/rfc7662#section-2.1 for more information.
- var principal = context.Request.TokenTypeHint switch
- {
- TokenTypeHints.AccessToken => await DeserializeAccessTokenAsync(),
- TokenTypeHints.AuthorizationCode => await DeserializeAuthorizationCodeAsync(),
- TokenTypeHints.IdToken => await DeserializeIdentityTokenAsync(),
- TokenTypeHints.RefreshToken => await DeserializeRefreshTokenAsync(),
-
- _ => null
- };
-
- // Note: if the revoked token can't be found using "token_type_hint",
- // the search must be extended to all supported token types.
- // See https://tools.ietf.org/html/rfc7662#section-2.1 for more information.
- // To avoid calling the same deserialization methods twice, an additional check
- // is made to exclude the corresponding call when a token_type_hint was specified.
- principal ??= context.Request.TokenTypeHint switch
- {
- TokenTypeHints.AccessToken => await DeserializeAuthorizationCodeAsync() ??
- await DeserializeIdentityTokenAsync() ??
- await DeserializeRefreshTokenAsync(),
-
- TokenTypeHints.AuthorizationCode => await DeserializeAccessTokenAsync() ??
- await DeserializeIdentityTokenAsync() ??
- await DeserializeRefreshTokenAsync(),
-
- TokenTypeHints.IdToken => await DeserializeAccessTokenAsync() ??
- await DeserializeAuthorizationCodeAsync() ??
- await DeserializeRefreshTokenAsync(),
-
- TokenTypeHints.RefreshToken => await DeserializeAccessTokenAsync() ??
- await DeserializeAuthorizationCodeAsync() ??
- await DeserializeIdentityTokenAsync(),
-
- _ => await DeserializeAccessTokenAsync() ??
- await DeserializeAuthorizationCodeAsync() ??
- await DeserializeIdentityTokenAsync() ??
- await DeserializeRefreshTokenAsync()
- };
+ var notification = new ProcessAuthenticationContext(context.Transaction);
+ await _provider.DispatchAsync(notification);
- if (principal == null)
+ if (notification.IsRequestHandled)
{
- context.Logger.LogError("The revocation request was rejected because the token was invalid.");
-
- context.Reject(
- error: Errors.InvalidToken,
- description: "The specified token is invalid.");
-
+ context.HandleRequest();
return;
}
- var date = principal.GetExpirationDate();
- if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
+ else if (notification.IsRequestSkipped)
{
- context.Logger.LogError("The revocation request was rejected because the token was expired.");
-
- context.Reject(
- error: Errors.InvalidToken,
- description: "The specified token is no longer valid.");
-
+ context.SkipRequest();
return;
}
- // Attach the principal extracted from the token to the parent event context.
- context.Principal = principal;
-
- async ValueTask DeserializeAccessTokenAsync()
- {
- var notification = new DeserializeAccessTokenContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
- }
-
- async ValueTask DeserializeAuthorizationCodeAsync()
- {
- var notification = new DeserializeAuthorizationCodeContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
- }
-
- async ValueTask DeserializeIdentityTokenAsync()
+ else if (notification.IsRejected)
{
- var notification = new DeserializeIdentityTokenContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
}
- async ValueTask DeserializeRefreshTokenAsync()
- {
- var notification = new DeserializeRefreshTokenContext(context.Transaction)
- {
- Token = context.Request.Token
- };
-
- await _provider.DispatchAsync(notification);
- return notification.Principal;
- }
+ // Attach the security principal extracted from the token to the
+ // validation context and store it as an environment property.
+ context.Principal = notification.Principal;
+ context.Transaction.Properties[Properties.AmbientPrincipal] = notification.Principal;
}
}
@@ -903,6 +817,111 @@ namespace OpenIddict.Server
}
}
+ ///
+ /// Contains the logic responsible of revoking the token sent by the client application.
+ /// Note: this handler is not used when the degraded mode is enabled.
+ ///
+ public class RevokeToken : IOpenIddictServerHandler
+ {
+ private readonly IOpenIddictTokenManager _tokenManager;
+
+ public RevokeToken() => throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
+ .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
+ .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
+ .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
+ .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
+ .ToString());
+
+ public RevokeToken([NotNull] IOpenIddictTokenManager tokenManager)
+ => _tokenManager = tokenManager;
+
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseScopedHandler()
+ .SetOrder(AttachPrincipal.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public async ValueTask HandleAsync([NotNull] HandleRevocationRequestContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // If the received token is not an authorization code or a refresh token,
+ // return an error to indicate that the token cannot be revoked.
+ if (context.Principal.IsIdentityToken())
+ {
+ context.Logger.LogError("The revocation request was rejected because identity tokens are not revocable.");
+
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "The specified token cannot be revoked.");
+
+ return;
+ }
+
+ // If the received token is an access token, return an error if reference tokens are not enabled.
+ if (context.Principal.IsAccessToken() && !context.Options.UseReferenceTokens)
+ {
+ context.Logger.LogError("The revocation request was rejected because the access token was not revocable.");
+
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "The specified token cannot be revoked.");
+
+ return;
+ }
+
+ // Extract the token identifier from the authentication principal.
+ var identifier = context.Principal.GetInternalTokenId();
+ if (string.IsNullOrEmpty(identifier))
+ {
+ context.Logger.LogError("The revocation request was rejected because the token had no internal identifier.");
+
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "The specified token cannot be revoked.");
+
+ return;
+ }
+
+ var token = await _tokenManager.FindByIdAsync(identifier);
+ if (token == null || await _tokenManager.IsRevokedAsync(token))
+ {
+ context.Logger.LogInformation("The token '{Identifier}' was not revoked because " +
+ "it was already marked as invalid.", identifier);
+
+ context.Reject(
+ error: Errors.InvalidToken,
+ description: "The specified token is invalid.");
+
+ return;
+ }
+
+ // Try to revoke the token. If an error occurs, return an error.
+ if (!await _tokenManager.TryRevokeAsync(token))
+ {
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "The specified token cannot be revoked.");
+
+ return;
+ }
+ }
+ }
+
///
/// Contains the logic responsible of converting revocation errors to standard empty responses.
///
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs
deleted file mode 100644
index b27aff1c..00000000
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs
+++ /dev/null
@@ -1,610 +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.Collections.Generic;
-using System.Collections.Immutable;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Security.Claims;
-using System.Threading.Tasks;
-using JetBrains.Annotations;
-using Microsoft.Extensions.Logging;
-using Microsoft.IdentityModel.JsonWebTokens;
-using Microsoft.IdentityModel.Tokens;
-using OpenIddict.Abstractions;
-using static OpenIddict.Abstractions.OpenIddictConstants;
-using static OpenIddict.Server.OpenIddictServerEvents;
-
-namespace OpenIddict.Server
-{
- public static partial class OpenIddictServerHandlers
- {
- public static class Serialization
- {
- public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create(
- /*
- * Access token serialization:
- */
- AttachAccessTokenSerializationParameters.Descriptor,
- SerializeJwtBearerToken.Descriptor,
-
- /*
- * Authorization code serialization:
- */
- AttachAuthorizationCodeSerializationParameters.Descriptor,
- SerializeJwtBearerToken.Descriptor,
-
- /*
- * Identity token serialization:
- */
- AttachIdentityTokenSerializationParameters.Descriptor,
- SerializeJwtBearerToken.Descriptor,
-
- /*
- * Refresh token serialization:
- */
- AttachRefreshTokenSerializationParameters.Descriptor,
- SerializeJwtBearerToken.Descriptor,
-
- /*
- * Access token deserialization:
- */
- AttachAccessTokenDeserializationParameters.Descriptor,
- DeserializeJwtBearerToken.Descriptor,
-
- /*
- * Authorization code deserialization:
- */
- AttachAuthorizationCodeDeserializationParameters.Descriptor,
- DeserializeJwtBearerToken.Descriptor,
-
- /*
- * Identity token deserialization:
- */
- AttachIdentityTokenDeserializationParameters.Descriptor,
- DeserializeJwtBearerToken.Descriptor,
-
- /*
- * Authorization code deserialization:
- */
- AttachRefreshTokenDeserializationParameters.Descriptor,
- DeserializeJwtBearerToken.Descriptor);
-
- ///
- /// Contains the logic responsible of generating a JWT bearer token using IdentityModel.
- ///
- public class SerializeJwtBearerToken : IOpenIddictServerHandler where TContext : BaseSerializingContext
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler>()
- .SetOrder(int.MaxValue - 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] TContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (string.IsNullOrEmpty(context.TokenUsage))
- {
- throw new InvalidOperationException("The token usage cannot be null or empty.");
- }
-
- var claims = new Dictionary(StringComparer.Ordinal)
- {
- [Claims.Private.TokenUsage] = context.TokenUsage
- };
-
- var destinations = new Dictionary(StringComparer.Ordinal);
- foreach (var group in context.Principal.Claims.GroupBy(claim => claim.Type))
- {
- var collection = group.ToList();
-
- // Note: destinations are attached to claims as special CLR properties. Such properties can't be serialized
- // as part of classic JWT tokens. To work around this limitation, claim destinations are added to a special
- // claim named oi_cl_dstn that contains a map of all the claims and their attached destinations, if any.
-
- var set = new HashSet(collection[0].GetDestinations(), StringComparer.OrdinalIgnoreCase);
- if (set.Count != 0)
- {
- // Ensure the other claims of the same type use the same exact destinations.
- for (var index = 0; index < collection.Count; index++)
- {
- if (!set.SetEquals(collection[index].GetDestinations()))
- {
- throw new InvalidOperationException($"Conflicting destinations for the claim '{group.Key}' were specified.");
- }
- }
-
- destinations[group.Key] = set.ToArray();
- }
- }
-
- // Unless at least one claim was added to the claim destinations map,
- // don't add the special claim to avoid adding a useless empty claim.
- if (destinations.Count != 0)
- {
- claims[Claims.Private.ClaimDestinations] = destinations;
- }
-
- context.Token = context.SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor
- {
- Subject = (ClaimsIdentity) context.Principal.Identity,
- Claims = new ReadOnlyDictionary(claims),
- EncryptingCredentials = context.EncryptingCredentials,
- Issuer = context.Issuer?.AbsoluteUri,
- SigningCredentials = context.SigningCredentials
- });
-
- context.HandleSerialization();
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of unprotecting a JWT bearer token using IdentityModel.
- ///
- public class DeserializeJwtBearerToken : IOpenIddictServerHandler where TContext : BaseDeserializingContext
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler>()
- .SetOrder(int.MaxValue - 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] TContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (!context.SecurityTokenHandler.CanReadToken(context.Token))
- {
- context.Logger.LogTrace("The token '{Token}' was not compatible with the JWT format.", context.Token);
-
- return default;
- }
-
- try
- {
- var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters);
- if (result == null || !result.IsValid)
- {
- if (result?.Exception != null)
- {
- context.Logger.LogTrace(result.Exception, "The JWT token '{Token}' could not be validated.", context.Token);
- }
-
- else
- {
- context.Logger.LogTrace("The token '{Token}' could not be validated.", context.Token);
- }
-
- return default;
- }
-
- var assertion = ((JsonWebToken) result.SecurityToken)?.InnerToken ?? (JsonWebToken) result.SecurityToken;
-
- if (!assertion.TryGetPayloadValue(Claims.Private.TokenUsage, out string usage) ||
- !string.Equals(usage, context.TokenUsage, StringComparison.OrdinalIgnoreCase))
- {
- context.Logger.LogDebug("The token usage associated to the token {Token} does not match the expected type.");
- context.HandleDeserialization();
-
- return default;
- }
-
- context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
-
- // Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object).
- if (assertion.TryGetPayloadValue(Claims.Private.ClaimDestinations, out IDictionary definitions))
- {
- foreach (var definition in definitions)
- {
- foreach (var claim in context.Principal.Claims.Where(claim => claim.Type == definition.Key))
- {
- claim.SetDestinations(definition.Value);
- }
- }
- }
-
- context.HandleDeserialization();
-
- return default;
- }
-
- catch (Exception exception)
- {
- context.Logger.LogDebug(exception, "An exception occured while deserializing a token.");
- context.HandleDeserialization();
-
- return default;
- }
- }
- }
-
- ///
- /// Contains the logic responsible of populating the serialization parameters needed to generate an access token.
- ///
- public class AttachAccessTokenSerializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] SerializeAccessTokenContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (context.Options.SigningCredentials.Count == 0)
- {
- throw new InvalidOperationException("No suitable signing credentials could be found.");
- }
-
- context.EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault(
- credentials => credentials.Key is SymmetricSecurityKey);
- context.SecurityTokenHandler = context.Options.AccessTokenHandler;
- context.SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(
- credentials => credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First();
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the serialization parameters needed to generate an authorization code.
- ///
- public class AttachAuthorizationCodeSerializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] SerializeAuthorizationCodeContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (context.Options.EncryptionCredentials.Count == 0)
- {
- throw new InvalidOperationException("No suitable encryption credentials could be found.");
- }
-
- if (context.Options.SigningCredentials.Count == 0)
- {
- throw new InvalidOperationException("No suitable signing credentials could be found.");
- }
-
- context.EncryptingCredentials = context.Options.EncryptionCredentials[0];
- context.SecurityTokenHandler = context.Options.AuthorizationCodeHandler;
- context.SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(
- credentials => credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First();
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the serialization parameters needed to generate an identity token.
- ///
- public class AttachIdentityTokenSerializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] SerializeIdentityTokenContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (!context.Options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey))
- {
- throw new InvalidOperationException("No suitable signing credentials could be found.");
- }
-
- context.SecurityTokenHandler = context.Options.IdentityTokenHandler;
- context.SigningCredentials = context.Options.SigningCredentials.First(
- credentials => credentials.Key is AsymmetricSecurityKey);
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the serialization parameters needed to generate a refresh token.
- ///
- public class AttachRefreshTokenSerializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] SerializeRefreshTokenContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (context.Options.EncryptionCredentials.Count == 0)
- {
- throw new InvalidOperationException("No suitable encryption credentials could be found.");
- }
-
- if (context.Options.SigningCredentials.Count == 0)
- {
- throw new InvalidOperationException("No suitable signing credentials could be found.");
- }
-
- context.EncryptingCredentials = context.Options.EncryptionCredentials[0];
- context.SecurityTokenHandler = context.Options.AuthorizationCodeHandler;
- context.SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(
- credentials => credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First();
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the deserialization parameters needed to unprotect an access token.
- ///
- public class AttachAccessTokenDeserializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] DeserializeAccessTokenContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- context.SecurityTokenHandler = context.Options.AccessTokenHandler;
-
- context.TokenValidationParameters.IssuerSigningKeys = context.Options.SigningCredentials
- .Select(credentials => credentials.Key);
- context.TokenValidationParameters.NameClaimType = Claims.Name;
- context.TokenValidationParameters.RoleClaimType = Claims.Role;
- context.TokenValidationParameters.TokenDecryptionKeys = context.Options.EncryptionCredentials
- .Select(credentials => credentials.Key)
- .Where(key => key is SymmetricSecurityKey);
- context.TokenValidationParameters.ValidIssuer = context.Issuer?.AbsoluteUri;
- context.TokenValidationParameters.ValidateAudience = false;
- context.TokenValidationParameters.ValidateLifetime = false;
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the deserialization parameters needed to unprotect an authorization code.
- ///
- public class AttachAuthorizationCodeDeserializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] DeserializeAuthorizationCodeContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- context.SecurityTokenHandler = context.Options.AuthorizationCodeHandler;
-
- context.TokenValidationParameters.IssuerSigningKeys = context.Options.SigningCredentials
- .Select(credentials => credentials.Key);
- context.TokenValidationParameters.NameClaimType = Claims.Name;
- context.TokenValidationParameters.RoleClaimType = Claims.Role;
- context.TokenValidationParameters.TokenDecryptionKeys = context.Options.EncryptionCredentials
- .Select(credentials => credentials.Key);
- context.TokenValidationParameters.ValidIssuer = context.Issuer?.AbsoluteUri;
- context.TokenValidationParameters.ValidateAudience = false;
- context.TokenValidationParameters.ValidateLifetime = false;
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the deserialization parameters needed to unprotect an identity token.
- ///
- public class AttachIdentityTokenDeserializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] DeserializeIdentityTokenContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- context.SecurityTokenHandler = context.Options.IdentityTokenHandler;
-
- context.TokenValidationParameters.IssuerSigningKeys = context.Options.SigningCredentials
- .Select(credentials => credentials.Key)
- .OfType();
- context.TokenValidationParameters.NameClaimType = Claims.Name;
- context.TokenValidationParameters.RoleClaimType = Claims.Role;
- context.TokenValidationParameters.ValidIssuer = context.Issuer?.AbsoluteUri;
- context.TokenValidationParameters.ValidateAudience = false;
- context.TokenValidationParameters.ValidateLifetime = false;
-
- return default;
- }
- }
-
- ///
- /// Contains the logic responsible of populating the deserialization parameters needed to unprotect a refresh token.
- ///
- public class AttachRefreshTokenDeserializationParameters : IOpenIddictServerHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
- .Build();
-
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] DeserializeRefreshTokenContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- context.SecurityTokenHandler = context.Options.AuthorizationCodeHandler;
-
- context.TokenValidationParameters.IssuerSigningKeys = context.Options.SigningCredentials
- .Select(credentials => credentials.Key);
- context.TokenValidationParameters.NameClaimType = Claims.Name;
- context.TokenValidationParameters.RoleClaimType = Claims.Role;
- context.TokenValidationParameters.TokenDecryptionKeys = context.Options.EncryptionCredentials
- .Select(credentials => credentials.Key);
- context.TokenValidationParameters.ValidIssuer = context.Issuer?.AbsoluteUri;
- context.TokenValidationParameters.ValidateAudience = false;
- context.TokenValidationParameters.ValidateLifetime = false;
-
- return default;
- }
- }
- }
- }
-}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
index f74f3f54..6f377e55 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
@@ -444,23 +444,29 @@ namespace OpenIddict.Server
return;
}
- var notification = new DeserializeIdentityTokenContext(context.Transaction)
- {
- Token = context.Request.IdTokenHint
- };
-
+ var notification = new ProcessAuthenticationContext(context.Transaction);
await _provider.DispatchAsync(notification);
- if (notification.Principal == null)
+ if (notification.IsRequestHandled)
{
- context.Reject(
- error: Errors.InvalidRequest,
- description: "The specified 'id_token_hint' parameter is invalid or malformed.");
+ context.HandleRequest();
+ return;
+ }
+ else if (notification.IsRequestSkipped)
+ {
+ context.SkipRequest();
return;
}
- // Note: the expiration date associated with an identity token used as an id_token_hint is deliberately ignored.
+ else if (notification.IsRejected)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
// Attach the security principal extracted from the identity token to the
// validation context and store it as an environment property.
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
index 77be8653..8ae93a95 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
@@ -39,7 +39,7 @@ namespace OpenIddict.Server
* Userinfo request validation:
*/
ValidateAccessTokenParameter.Descriptor,
- ValidateAccessToken.Descriptor,
+ ValidateToken.Descriptor,
/*
* Userinfo request handling:
@@ -392,13 +392,13 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of rejecting userinfo requests that specify an invalid access token.
+ /// Contains the logic responsible of rejecting userinfo requests that don't specify a valid token.
///
- public class ValidateAccessToken : IOpenIddictServerHandler
+ public class ValidateToken : IOpenIddictServerHandler
{
private readonly IOpenIddictServerProvider _provider;
- public ValidateAccessToken([NotNull] IOpenIddictServerProvider provider)
+ public ValidateToken([NotNull] IOpenIddictServerProvider provider)
=> _provider = provider;
///
@@ -406,8 +406,8 @@ namespace OpenIddict.Server
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseScopedHandler()
- .SetOrder(100_000)
+ .UseScopedHandler()
+ .SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000)
.Build();
///
@@ -424,38 +424,34 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- var notification = new DeserializeAccessTokenContext(context.Transaction)
- {
- Token = context.Request.AccessToken
- };
-
+ var notification = new ProcessAuthenticationContext(context.Transaction);
await _provider.DispatchAsync(notification);
- if (notification.Principal == null)
+ if (notification.IsRequestHandled)
{
- context.Logger.LogError("The userinfo request was rejected because the access token was invalid.");
-
- context.Reject(
- error: Errors.InvalidToken,
- description: "The specified access token is invalid.");
-
+ context.HandleRequest();
return;
}
- var date = notification.Principal.GetExpirationDate();
- if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
+ else if (notification.IsRequestSkipped)
{
- context.Logger.LogError("The userinfo request was rejected because the access token was expired.");
+ context.SkipRequest();
+ return;
+ }
+ else if (notification.IsRejected)
+ {
context.Reject(
- error: Errors.InvalidToken,
- description: "The specified access token is no longer valid.");
-
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
return;
}
- // Attach the principal extracted from the authorization code to the parent event context.
+ // Attach the security principal extracted from the token to the
+ // validation context and store it as an environment property.
context.Principal = notification.Principal;
+ context.Transaction.Properties[Properties.AmbientPrincipal] = notification.Principal;
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 8b1bcade..eb51dcf8 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -5,6 +5,7 @@
*/
using System;
+using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
@@ -30,47 +31,72 @@ namespace OpenIddict.Server
/*
* Authentication processing:
*/
- AttachAmbientPrincipal.Descriptor,
+ ValidateAuthenticationDemand.Descriptor,
+ ValidateReferenceToken.Descriptor,
+ ValidateSelfContainedToken.Descriptor,
+ ValidatePrincipal.Descriptor,
+ ValidateTokenEntry.Descriptor,
+ ValidateAuthorizationEntry.Descriptor,
+ ValidateExpirationDate.Descriptor,
/*
* Challenge processing:
*/
AttachDefaultChallengeError.Descriptor,
-
+
/*
* Sign-in processing:
*/
ValidateSigninResponse.Descriptor,
+ RestoreInternalClaims.Descriptor,
AttachDefaultScopes.Descriptor,
AttachDefaultPresenters.Descriptor,
InferResources.Descriptor,
EvaluateReturnedTokens.Descriptor,
AttachAuthorization.Descriptor,
- AttachAccessToken.Descriptor,
- AttachAuthorizationCode.Descriptor,
- AttachRefreshToken.Descriptor,
- AttachIdentityToken.Descriptor)
+
+ PrepareAccessTokenPrincipal.Descriptor,
+ PrepareAuthorizationCodePrincipal.Descriptor,
+ PrepareRefreshTokenPrincipal.Descriptor,
+ PrepareIdentityTokenPrincipal.Descriptor,
+
+ RedeemTokenEntry.Descriptor,
+ RevokeRollingTokenEntries.Descriptor,
+ ExtendRefreshTokenEntry.Descriptor,
+
+ AttachReferenceAccessToken.Descriptor,
+ AttachReferenceAuthorizationCode.Descriptor,
+ AttachReferenceRefreshToken.Descriptor,
+
+ CreateSelfContainedAuthorizationCodeEntry.Descriptor,
+ CreateSelfContainedRefreshTokenEntry.Descriptor,
+
+ AttachSelfContainedAccessToken.Descriptor,
+ AttachSelfContainedAuthorizationCode.Descriptor,
+ AttachSelfContainedRefreshToken.Descriptor,
+
+ AttachTokenDigests.Descriptor,
+ AttachSelfContainedIdentityToken.Descriptor)
.AddRange(Authentication.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers)
.AddRange(Revocation.DefaultHandlers)
- .AddRange(Serialization.DefaultHandlers)
.AddRange(Session.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);
///
- /// Contains the logic responsible of attaching the ambient principal resolved for the current request.
+ /// Contains the logic responsible of rejecting authentication demands made from unsupported endpoints.
///
- public class AttachAmbientPrincipal : IOpenIddictServerHandler
+ public class ValidateAuthenticationDemand : IOpenIddictServerHandler
{
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
+ .UseSingletonHandler()
.SetOrder(int.MinValue + 100_000)
.Build();
@@ -91,93 +117,250 @@ namespace OpenIddict.Server
switch (context.EndpointType)
{
case OpenIddictServerEndpointType.Authorization:
+ case OpenIddictServerEndpointType.Introspection:
case OpenIddictServerEndpointType.Logout:
+ case OpenIddictServerEndpointType.Revocation:
case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType():
case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType():
case OpenIddictServerEndpointType.Userinfo:
- {
- if (context.Transaction.Properties.TryGetValue(Properties.AmbientPrincipal, out var principal))
- {
- context.Principal = (ClaimsPrincipal) principal;
- }
-
return default;
- }
- default: throw new InvalidOperationException("An identity cannot be extracted from this request.");
+ default: throw new InvalidOperationException("No identity cannot be extracted from this request.");
}
}
}
///
- /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error.
+ /// Contains the logic responsible of rejecting authentication demands that use an invalid reference token.
+ /// Note: this handler is not used when the degraded mode is enabled.
///
- public class AttachDefaultChallengeError : IOpenIddictServerHandler
+ public class ValidateReferenceToken : IOpenIddictServerHandler
{
+ private readonly IOpenIddictTokenManager _tokenManager;
+
+ public ValidateReferenceToken() => throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
+ .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
+ .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
+ .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
+ .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
+ .ToString());
+
+ public ValidateReferenceToken([NotNull] IOpenIddictTokenManager tokenManager)
+ => _tokenManager = tokenManager;
+
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .AddFilter()
+ .AddFilter()
+ .UseScopedHandler()
+ .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 1_000)
.Build();
- ///
- /// Processes the event.
- ///
- /// The context associated with the event to process.
- ///
- /// A that can be used to monitor the asynchronous operation.
- ///
- public ValueTask HandleAsync([NotNull] ProcessChallengeContext context)
+ public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
- if (string.IsNullOrEmpty(context.Response.Error))
+ // If a principal was already attached, don't overwrite it.
+ if (context.Principal != null)
{
- context.Response.Error = context.EndpointType switch
+ return;
+ }
+
+ var identifier = context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Introspection => context.Request.Token,
+ OpenIddictServerEndpointType.Revocation => context.Request.Token,
+
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => context.Request.Code,
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => context.Request.RefreshToken,
+
+ OpenIddictServerEndpointType.Userinfo => context.Request.AccessToken,
+
+ _ => null
+ };
+
+ // If the token cannot be validated, don't return an error to allow another handle to validate it.
+ if (string.IsNullOrEmpty(identifier))
+ {
+ return;
+ }
+
+ // If the reference token cannot be found, return a generic error.
+ var token = await _tokenManager.FindByReferenceIdAsync(identifier);
+ if (token == null || !await IsTokenTypeValidAsync(token))
+ {
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The specified authorization code is not valid.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The specified refresh token is not valid.",
+
+ _ => "The specified token is not valid."
+ });
+
+ return;
+ }
+
+ var payload = await _tokenManager.GetPayloadAsync(token);
+ if (string.IsNullOrEmpty(payload))
+ {
+ throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The payload associated with a reference token cannot be retrieved.")
+ .Append("This may indicate that the token entry was corrupted.")
+ .ToString());
+ }
+
+ // If the token cannot be validated, don't return an error to allow another handle to validate it.
+ if (!context.Options.SecurityTokenHandler.CanReadToken(payload))
+ {
+ return;
+ }
+
+ var result = context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Introspection => await ValidateAnyTokenAsync(payload),
+ OpenIddictServerEndpointType.Revocation => await ValidateAnyTokenAsync(payload),
+
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => await ValidateTokenAsync(payload, TokenUsages.AuthorizationCode),
+
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => await ValidateTokenAsync(payload, TokenUsages.RefreshToken),
+
+ OpenIddictServerEndpointType.Userinfo => await ValidateTokenAsync(payload, TokenUsages.AccessToken),
+
+ _ => new TokenValidationResult { IsValid = false }
+ };
+
+ // If the token cannot be validated, don't return an error to allow another handle to validate it.
+ if (result.ClaimsIdentity == null)
+ {
+ return;
+ }
+
+ // Attach the principal extracted from the authorization code to the parent event context
+ // and restore the creation/expiration dates/identifiers from the token entry metadata.
+ context.Principal = new ClaimsPrincipal(result.ClaimsIdentity)
+ .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
+ .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
+ .SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
+ .SetInternalTokenId(await _tokenManager.GetIdAsync(token))
+ .SetClaim(Claims.Private.TokenUsage, await _tokenManager.GetTypeAsync(token));
+
+ async ValueTask ValidateTokenAsync(string token, string type)
+ {
+ var parameters = new TokenValidationParameters
{
- OpenIddictServerEndpointType.Authorization => Errors.AccessDenied,
- OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
- OpenIddictServerEndpointType.Userinfo => Errors.InvalidToken,
+ NameClaimType = Claims.Name,
+ PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = type },
+ RoleClaimType = Claims.Role,
+ ValidIssuer = context.Issuer?.AbsoluteUri,
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
- _ => throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.")
+ parameters.IssuerSigningKeys = type switch
+ {
+ TokenUsages.AccessToken => context.Options.SigningCredentials.Select(credentials => credentials.Key),
+ TokenUsages.AuthorizationCode => context.Options.SigningCredentials.Select(credentials => credentials.Key),
+ TokenUsages.RefreshToken => context.Options.SigningCredentials.Select(credentials => credentials.Key),
+
+ _ => Array.Empty()
+ };
+
+ parameters.TokenDecryptionKeys = type switch
+ {
+ TokenUsages.AuthorizationCode => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
+ TokenUsages.RefreshToken => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
+
+ TokenUsages.AccessToken => context.Options.EncryptionCredentials
+ .Select(credentials => credentials.Key)
+ .Where(key => key is SymmetricSecurityKey),
+
+ _ => Array.Empty()
};
+
+ return await context.Options.SecurityTokenHandler.ValidateTokenStringAsync(token, parameters);
}
- if (string.IsNullOrEmpty(context.Response.ErrorDescription))
+ async ValueTask ValidateAnyTokenAsync(string token)
{
- context.Response.ErrorDescription = context.EndpointType switch
+ var result = await ValidateTokenAsync(token, TokenUsages.AccessToken);
+ if (result.IsValid)
{
- OpenIddictServerEndpointType.Authorization => "The authorization was denied by the resource owner.",
- OpenIddictServerEndpointType.Token => "The token request was rejected by the authorization server.",
- OpenIddictServerEndpointType.Userinfo => "The access token is not valid or cannot be used to retrieve user information.",
+ return result;
+ }
- _ => throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.")
- };
+ result = await ValidateTokenAsync(token, TokenUsages.RefreshToken);
+ if (result.IsValid)
+ {
+ return result;
+ }
+
+ result = await ValidateTokenAsync(token, TokenUsages.AuthorizationCode);
+ if (result.IsValid)
+ {
+ return result;
+ }
+
+ return new TokenValidationResult { IsValid = false };
}
- return default;
+ async ValueTask IsTokenTypeValidAsync(object token) => context.EndpointType switch
+ {
+ // All types of tokens are accepted by the introspection and revocation endpoints.
+ OpenIddictServerEndpointType.Introspection => true,
+ OpenIddictServerEndpointType.Revocation => true,
+
+ OpenIddictServerEndpointType.Token => await _tokenManager.GetTypeAsync(token) switch
+ {
+ TokenUsages.AuthorizationCode when context.Request.IsAuthorizationCodeGrantType() => true,
+ TokenUsages.RefreshToken when context.Request.IsRefreshTokenGrantType() => true,
+
+ _ => false
+ },
+
+ OpenIddictServerEndpointType.Userinfo => await _tokenManager.GetTypeAsync(token) switch
+ {
+ TokenUsages.AccessToken => true,
+
+ _ => false
+ },
+
+ _ => false
+ };
}
}
///
- /// Contains the logic responsible of ensuring that the sign-in response
- /// is compatible with the type of the endpoint that handled the request.
+ /// Contains the logic responsible of rejecting authentication demands that specify an invalid self-contained token.
///
- public class ValidateSigninResponse : IOpenIddictServerHandler
+ public class ValidateSelfContainedToken : IOpenIddictServerHandler
{
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_000)
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateReferenceToken.Descriptor.Order + 1_000)
.Build();
///
@@ -187,54 +370,166 @@ namespace OpenIddict.Server
///
/// A that can be used to monitor the asynchronous operation.
///
- public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
- switch (context.EndpointType)
+ // If a principal was already attached, don't overwrite it.
+ if (context.Principal != null)
{
- case OpenIddictServerEndpointType.Authorization:
- case OpenIddictServerEndpointType.Token:
- break;
+ return;
+ }
- default: throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.");
+ var token = context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Authorization => context.Request.IdTokenHint,
+ OpenIddictServerEndpointType.Logout => context.Request.IdTokenHint,
+
+ OpenIddictServerEndpointType.Introspection => context.Request.Token,
+ OpenIddictServerEndpointType.Revocation => context.Request.Token,
+
+ // This handler doesn't handle reference tokens.
+ _ when context.Options.UseReferenceTokens => null,
+
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => context.Request.Code,
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => context.Request.RefreshToken,
+
+ OpenIddictServerEndpointType.Userinfo => context.Request.AccessToken,
+
+ _ => null
+ };
+
+ // If the token cannot be validated, don't return an error to allow another handle to validate it.
+ if (string.IsNullOrEmpty(token) || !context.Options.SecurityTokenHandler.CanReadToken(token))
+ {
+ return;
}
- if (context.Principal.Identity == null || !context.Principal.Identity.IsAuthenticated)
+ var result = context.EndpointType switch
{
- throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The specified principal doesn't contain a valid or authenticated identity.")
- .Append("Make sure that both 'ClaimsPrincipal.Identity' and 'ClaimsPrincipal.Identity.AuthenticationType' ")
- .Append("are not null and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.")
- .ToString());
+ OpenIddictServerEndpointType.Authorization => await ValidateTokenAsync(token, TokenUsages.IdToken),
+ OpenIddictServerEndpointType.Logout => await ValidateTokenAsync(token, TokenUsages.IdToken),
+
+ // When reference tokens are enabled, this handler can only validate id_tokens.
+ OpenIddictServerEndpointType.Introspection when context.Options.UseReferenceTokens
+ => await ValidateTokenAsync(token, TokenUsages.IdToken),
+
+ OpenIddictServerEndpointType.Revocation when context.Options.UseReferenceTokens
+ => await ValidateTokenAsync(token, TokenUsages.IdToken),
+
+ _ when context.Options.UseReferenceTokens => new TokenValidationResult { IsValid = false },
+
+ OpenIddictServerEndpointType.Introspection => await ValidateAnyTokenAsync(token),
+ OpenIddictServerEndpointType.Revocation => await ValidateAnyTokenAsync(token),
+
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => await ValidateTokenAsync(token, TokenUsages.AuthorizationCode),
+
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => await ValidateTokenAsync(token, TokenUsages.RefreshToken),
+
+ OpenIddictServerEndpointType.Userinfo => await ValidateTokenAsync(token, TokenUsages.AccessToken),
+
+ _ => new TokenValidationResult { IsValid = false }
+ };
+
+ // If the token cannot be validated, don't return an error to allow another handle to validate it.
+ if (result.ClaimsIdentity == null)
+ {
+ return;
}
- if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject)))
+ // Attach the principal extracted from the token to the parent event context.
+ context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
+
+ async ValueTask ValidateTokenAsync(string token, string type)
{
- throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The security principal was rejected because the mandatory subject claim was missing.")
- .ToString());
+ var parameters = new TokenValidationParameters
+ {
+ NameClaimType = Claims.Name,
+ PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = type },
+ RoleClaimType = Claims.Role,
+ ValidIssuer = context.Issuer?.AbsoluteUri,
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
+
+ parameters.IssuerSigningKeys = type switch
+ {
+ TokenUsages.AccessToken => context.Options.SigningCredentials.Select(credentials => credentials.Key),
+ TokenUsages.AuthorizationCode => context.Options.SigningCredentials.Select(credentials => credentials.Key),
+ TokenUsages.RefreshToken => context.Options.SigningCredentials.Select(credentials => credentials.Key),
+
+ TokenUsages.IdToken => context.Options.SigningCredentials
+ .Select(credentials => credentials.Key)
+ .OfType(),
+
+ _ => Array.Empty()
+ };
+
+ parameters.TokenDecryptionKeys = type switch
+ {
+ TokenUsages.AuthorizationCode => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
+ TokenUsages.RefreshToken => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
+
+ TokenUsages.AccessToken => context.Options.EncryptionCredentials
+ .Select(credentials => credentials.Key)
+ .Where(key => key is SymmetricSecurityKey),
+
+ _ => Array.Empty()
+ };
+
+ return await context.Options.SecurityTokenHandler.ValidateTokenStringAsync(token, parameters);
}
- return default;
+ async ValueTask ValidateAnyTokenAsync(string token)
+ {
+ var result = await ValidateTokenAsync(token, TokenUsages.AccessToken);
+ if (result.IsValid)
+ {
+ return result;
+ }
+
+ result = await ValidateTokenAsync(token, TokenUsages.RefreshToken);
+ if (result.IsValid)
+ {
+ return result;
+ }
+
+ result = await ValidateTokenAsync(token, TokenUsages.AuthorizationCode);
+ if (result.IsValid)
+ {
+ return result;
+ }
+
+ result = await ValidateTokenAsync(token, TokenUsages.IdToken);
+ if (result.IsValid)
+ {
+ return result;
+ }
+
+ return new TokenValidationResult { IsValid = false };
+ }
}
}
///
- /// Contains the logic responsible of attaching default scopes to the authentication principal.
+ /// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved.
///
- public class AttachDefaultScopes : IOpenIddictServerHandler
+ public class ValidatePrincipal : IOpenIddictServerHandler
{
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(ValidateSigninResponse.Descriptor.Order + 1_000)
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateSelfContainedToken.Descriptor.Order + 1_000)
.Build();
///
@@ -244,19 +539,36 @@ namespace OpenIddict.Server
///
/// A that can be used to monitor the asynchronous operation.
///
- public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ public ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
- // Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
- // Note: the application is allowed to specify a different "scopes": in this case,
- // don't replace the "scopes" property stored in the authentication ticket.
- if (!context.Principal.HasScope() && context.Request.HasScope(Scopes.OpenId))
+ if (context.Principal == null)
{
- context.Principal.SetScopes(Scopes.OpenId);
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Authorization => "The specified identity token hint is not valid.",
+ OpenIddictServerEndpointType.Logout => "The specified identity token hint is not valid.",
+
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The specified authorization code is not valid.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The specified refresh token is not valid.",
+
+ _ => "The specified token is not valid."
+ });
+
+
+ return default;
}
return default;
@@ -264,17 +576,1430 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of attaching default presenters to the authentication principal.
+ /// Contains the logic responsible of rejecting authentication demands that
+ /// use a token whose entry is no longer valid (e.g was revoked).
+ /// Note: this handler is not used when the degraded mode is enabled.
///
- public class AttachDefaultPresenters : IOpenIddictServerHandler
+ public class ValidateTokenEntry : IOpenIddictServerHandler
{
+ private readonly IOpenIddictAuthorizationManager _authorizationManager;
+ private readonly IOpenIddictTokenManager _tokenManager;
+
+ public ValidateTokenEntry() => throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
+ .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
+ .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
+ .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
+ .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
+ .ToString());
+
+ public ValidateTokenEntry(
+ [NotNull] IOpenIddictAuthorizationManager authorizationManager,
+ [NotNull] IOpenIddictTokenManager tokenManager)
+ {
+ _authorizationManager = authorizationManager;
+ _tokenManager = tokenManager;
+ }
+
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(AttachDefaultScopes.Descriptor.Order + 1_000)
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .AddFilter()
+ .UseScopedHandler()
+ .SetOrder(ValidatePrincipal.Descriptor.Order + 1_000)
+ .Build();
+
+ public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Extract the token identifier from the authentication principal.
+ // If no token identifier can be found, this indicates that the token
+ // has no backing database entry (e.g an access token or an identity token).
+ var identifier = context.Principal.GetInternalTokenId();
+ if (string.IsNullOrEmpty(identifier))
+ {
+ return;
+ }
+
+ // If the token entry cannot be found, return a generic error.
+ var token = await _tokenManager.FindByIdAsync(identifier);
+ if (token == null)
+ {
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The specified authorization code is not valid.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The specified refresh token is not valid.",
+
+ _ => "The specified token is not valid."
+ });
+
+ return;
+ }
+
+ // If the authorization code/refresh token is already marked as redeemed, this may indicate that
+ // it was compromised. In this case, revoke the authorization and all the associated tokens.
+ // See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
+ if (context.EndpointType == OpenIddictServerEndpointType.Token &&
+ (context.Request.IsAuthorizationCodeGrantType() || context.Request.IsRefreshTokenGrantType()) &&
+ await _tokenManager.IsRedeemedAsync(token))
+ {
+ await TryRevokeAuthorizationChainAsync(token);
+
+ context.Logger.LogError("The token '{Identifier}' has already been redeemed.", identifier);
+
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The specified authorization code has already been redeemed.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The specified refresh token has already been redeemed.",
+
+ _ => "The specified token has already been redeemed."
+ });
+
+ return;
+ }
+
+ if (!await _tokenManager.IsValidAsync(token))
+ {
+ context.Logger.LogError("The token '{Identifier}' was no longer valid.", identifier);
+
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The specified authorization code is no longer valid.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The specified refresh token is no longer valid.",
+
+ _ => "The specified token is no longer valid."
+ });
+
+ return;
+ }
+
+ // Restore the creation/expiration dates/identifiers from the token entry metadata.
+ context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
+ .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
+ .SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
+ .SetInternalTokenId(await _tokenManager.GetIdAsync(token))
+ .SetClaim(Claims.Private.TokenUsage, await _tokenManager.GetTypeAsync(token));
+
+ async ValueTask TryRevokeAuthorizationChainAsync(object token)
+ {
+ // First, mark the redeemed token submitted by the client as revoked.
+ await _tokenManager.TryRevokeAsync(token);
+
+ var identifier = context.Principal.GetInternalAuthorizationId();
+ if (context.Options.DisableAuthorizationStorage || string.IsNullOrEmpty(identifier))
+ {
+ return;
+ }
+
+ // Then, try to revoke the authorization and the associated token entries.
+
+ var authorization = await _authorizationManager.FindByIdAsync(identifier);
+ if (authorization != null)
+ {
+ await _authorizationManager.TryRevokeAsync(authorization);
+ }
+
+ await using var enumerator = _tokenManager.FindByAuthorizationIdAsync(identifier).GetAsyncEnumerator();
+ while (await enumerator.MoveNextAsync())
+ {
+ // Don't change the status of the token used in the token request.
+ if (string.Equals(context.Principal.GetInternalTokenId(),
+ await _tokenManager.GetIdAsync(enumerator.Current), StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ await _tokenManager.TryRevokeAsync(token);
+ }
+ }
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ public class ValidateAuthorizationEntry : IOpenIddictServerHandler
+ {
+ private readonly IOpenIddictAuthorizationManager _authorizationManager;
+
+ public ValidateAuthorizationEntry() => throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
+ .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
+ .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
+ .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
+ .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
+ .ToString());
+
+ public ValidateAuthorizationEntry([NotNull] IOpenIddictAuthorizationManager authorizationManager)
+ => _authorizationManager = authorizationManager;
+
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .AddFilter()
+ .UseScopedHandler()
+ .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
+ .Build();
+
+ public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var identifier = context.Principal.GetInternalAuthorizationId();
+ if (string.IsNullOrEmpty(identifier))
+ {
+ return;
+ }
+
+ var authorization = await _authorizationManager.FindByIdAsync(identifier);
+ if (authorization == null || !await _authorizationManager.IsValidAsync(authorization))
+ {
+ context.Logger.LogError("The authorization associated with token '{Identifier}' " +
+ "was no longer valid.", context.Principal.GetInternalTokenId());
+
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The authorization associated with the authorization code is no longer valid.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The authorization associated with the refresh token is no longer valid.",
+
+ _ => "The authorization associated with the token is no longer valid."
+ });
+
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of rejecting authentication demands that use an expired token.
+ ///
+ public class ValidateExpirationDate : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Don't validate the lifetime of id_tokens used as id_token_hints.
+ switch (context.EndpointType)
+ {
+ case OpenIddictServerEndpointType.Authorization:
+ case OpenIddictServerEndpointType.Logout:
+ return default;
+ }
+
+ var date = context.Principal.GetExpirationDate();
+ if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
+ {
+ context.Reject(
+ error: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ _ => Errors.InvalidToken
+ },
+ description: context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
+ => "The specified authorization code is no longer valid.",
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
+ => "The specified refresh token is no longer valid.",
+
+ _ => "The specified token is no longer valid."
+ });
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error.
+ ///
+ public class AttachDefaultChallengeError : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(int.MinValue + 100_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessChallengeContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Response.Error = context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Authorization => Errors.AccessDenied,
+ OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
+ OpenIddictServerEndpointType.Userinfo => Errors.InvalidToken,
+
+ _ => throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.")
+ };
+ }
+
+ if (string.IsNullOrEmpty(context.Response.ErrorDescription))
+ {
+ context.Response.ErrorDescription = context.EndpointType switch
+ {
+ OpenIddictServerEndpointType.Authorization => "The authorization was denied by the resource owner.",
+ OpenIddictServerEndpointType.Token => "The token request was rejected by the authorization server.",
+ OpenIddictServerEndpointType.Userinfo => "The access token is not valid or cannot be used to retrieve user information.",
+
+ _ => throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.")
+ };
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of ensuring that the sign-in response
+ /// is compatible with the type of the endpoint that handled the request.
+ ///
+ public class ValidateSigninResponse : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(int.MinValue + 100_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ switch (context.EndpointType)
+ {
+ case OpenIddictServerEndpointType.Authorization:
+ case OpenIddictServerEndpointType.Token:
+ break;
+
+ default: throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.");
+ }
+
+ if (context.Principal.Identity == null || !context.Principal.Identity.IsAuthenticated)
+ {
+ throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The specified principal doesn't contain a valid or authenticated identity.")
+ .Append("Make sure that both 'ClaimsPrincipal.Identity' and 'ClaimsPrincipal.Identity.AuthenticationType' ")
+ .Append("are not null and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.")
+ .ToString());
+ }
+
+ if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject)))
+ {
+ throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The security principal was rejected because the mandatory subject claim was missing.")
+ .ToString());
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of re-attaching internal claims to the authentication principal.
+ ///
+ public class RestoreInternalClaims : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateSigninResponse.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (context.EndpointType != OpenIddictServerEndpointType.Token)
+ {
+ return default;
+ }
+
+ if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
+ {
+ return default;
+ }
+
+ if (!context.Transaction.Properties.TryGetValue(Properties.OriginalPrincipal, out var principal))
+ {
+ throw new InvalidOperationException("The original principal cannot be resolved from the transaction.");
+ }
+
+ // Restore the internal claims resolved from the authorization code/refresh token.
+ foreach (var claims in ((ClaimsPrincipal) principal).Claims
+ .Where(claim => claim.Type.StartsWith(Claims.Prefixes.Private))
+ .GroupBy(claim => claim.Type))
+ {
+ // If the specified principal already contains one claim of the iterated type, ignore them.
+ if (context.Principal.Claims.Any(claim => claim.Type == claims.Key))
+ {
+ continue;
+ }
+
+ ((ClaimsIdentity) context.Principal.Identity).AddClaims(claims);
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of attaching default scopes to the authentication principal.
+ ///
+ public class AttachDefaultScopes : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
+ // Note: the application is allowed to specify a different "scopes": in this case,
+ // don't replace the "scopes" property stored in the authentication ticket.
+ if (!context.Principal.HasScope() && context.Request.HasScope(Scopes.OpenId))
+ {
+ context.Principal.SetScopes(Scopes.OpenId);
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of attaching default presenters to the authentication principal.
+ ///
+ public class AttachDefaultPresenters : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(AttachDefaultScopes.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Add the validated client_id to the list of authorized presenters,
+ // unless the presenters were explicitly set by the developer.
+ if (!context.Principal.HasPresenter() && !string.IsNullOrEmpty(context.ClientId))
+ {
+ context.Principal.SetPresenters(context.ClientId);
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of inferring resources from the audience claims if necessary.
+ ///
+ public class InferResources : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(AttachDefaultPresenters.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // When a "resources" property cannot be found in the ticket, infer it from the "audiences" property.
+ if (context.Principal.HasAudience() && !context.Principal.HasResource())
+ {
+ context.Principal.SetResources(context.Principal.GetAudiences());
+ }
+
+ // Reset the audiences collection, as it's later set, based on the token type.
+ context.Principal.SetAudiences(Array.Empty());
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of selecting the token types returned to the client application.
+ ///
+ public class EvaluateReturnedTokens : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(InferResources.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ context.IncludeAccessToken = context.EndpointType switch
+ {
+ // For authorization requests, return an access token if a response type containing token was specified.
+ OpenIddictServerEndpointType.Authorization => context.Request.HasResponseType(ResponseTypes.Token),
+
+ // For token requests, always return an access token.
+ OpenIddictServerEndpointType.Token => true,
+
+ _ => false
+ };
+
+ context.IncludeAuthorizationCode = context.EndpointType switch
+ {
+ // For authorization requests, return an authorization code if a response type containing code was specified.
+ OpenIddictServerEndpointType.Authorization => context.Request.HasResponseType(ResponseTypes.Code),
+
+ // For token requests, prevent an authorization code from being returned as this type of token
+ // cannot be issued from the token endpoint in the standard OAuth 2.0/OpenID Connect flows.
+ OpenIddictServerEndpointType.Token => false,
+
+ _ => false
+ };
+
+ context.IncludeRefreshToken = context.EndpointType switch
+ {
+ // For authorization requests, prevent a refresh token from being returned as OAuth 2.0
+ // explicitly disallows returning a refresh token from the authorization endpoint.
+ // See https://tools.ietf.org/html/rfc6749#section-4.2.2 for more information.
+ OpenIddictServerEndpointType.Authorization => false,
+
+ // For token requests, never return a refresh token if the offline_access scope was not granted.
+ OpenIddictServerEndpointType.Token when !context.Principal.HasScope(Scopes.OfflineAccess) => false,
+
+ // For grant_type=refresh_token token requests, only return a refresh token if rolling tokens are enabled.
+ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() => context.Options.UseRollingTokens,
+
+ // For token requests that don't meet the previous criteria, allow a refresh token to be returned.
+ OpenIddictServerEndpointType.Token => true,
+
+ _ => false
+ };
+
+ context.IncludeIdentityToken = context.EndpointType switch
+ {
+ // For authorization requests, return an identity token if a response type containing code
+ // was specified and if the openid scope was explicitly or implicitly granted.
+ OpenIddictServerEndpointType.Authorization => context.Principal.HasScope(Scopes.OpenId) &&
+ context.Request.HasResponseType(ResponseTypes.IdToken),
+
+ // For token requests, only return an identity token if the openid scope was granted.
+ OpenIddictServerEndpointType.Token => context.Principal.HasScope(Scopes.OpenId),
+
+ _ => false
+ };
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of creating an ad-hoc authorization, if necessary.
+ /// Note: this handler is not used when the degraded mode is enabled.
+ ///
+ public class AttachAuthorization : IOpenIddictServerHandler
+ {
+ private readonly IOpenIddictApplicationManager _applicationManager;
+ private readonly IOpenIddictAuthorizationManager _authorizationManager;
+
+ public AttachAuthorization() => throw new InvalidOperationException(new StringBuilder()
+ .AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
+ .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
+ .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
+ .Append("Alternatively, you can disable the built-in database-based server features by enabling ")
+ .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
+ .ToString());
+
+ public AttachAuthorization(
+ [NotNull] IOpenIddictApplicationManager applicationManager,
+ [NotNull] IOpenIddictAuthorizationManager authorizationManager)
+ {
+ _applicationManager = applicationManager;
+ _authorizationManager = authorizationManager;
+ }
+
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .AddFilter()
+ .UseScopedHandler()
+ .SetOrder(EvaluateReturnedTokens.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public async ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // If no authorization code or refresh token is returned, don't create an authorization.
+ if (!context.IncludeAuthorizationCode && !context.IncludeRefreshToken)
+ {
+ return;
+ }
+
+ // If an authorization identifier was explicitly specified, don't create an ad-hoc authorization.
+ if (!string.IsNullOrEmpty(context.Principal.GetInternalAuthorizationId()))
+ {
+ return;
+ }
+
+ var descriptor = new OpenIddictAuthorizationDescriptor
+ {
+ Principal = context.Principal,
+ Status = Statuses.Valid,
+ Subject = context.Principal.GetClaim(Claims.Subject),
+ Type = AuthorizationTypes.AdHoc
+ };
+
+ descriptor.Scopes.UnionWith(context.Principal.GetScopes());
+
+ // If the client application is known, associate it to the authorization.
+ if (!string.IsNullOrEmpty(context.Request.ClientId))
+ {
+ var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId);
+ if (application == null)
+ {
+ throw new InvalidOperationException("The application entry cannot be found in the database.");
+ }
+
+ descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
+ }
+
+ var authorization = await _authorizationManager.CreateAsync(descriptor);
+ if (authorization == null)
+ {
+ return;
+ }
+
+ var identifier = await _authorizationManager.GetIdAsync(authorization);
+
+ if (string.IsNullOrEmpty(context.Request.ClientId))
+ {
+ context.Logger.LogInformation("An ad hoc authorization was automatically created and " +
+ "associated with an unknown application: {Identifier}.", identifier);
+ }
+
+ else
+ {
+ context.Logger.LogInformation("An ad hoc authorization was automatically created and " +
+ "associated with the '{ClientId}' application: {Identifier}.",
+ context.Request.ClientId, identifier);
+ }
+
+ // Attach the unique identifier of the ad hoc authorization to the authentication principal
+ // so that it is attached to all the derived tokens, allowing batched revocations support.
+ context.Principal.SetInternalAuthorizationId(identifier);
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of preparing and attaching the claims principal
+ /// used to generate the access token, if one is going to be returned.
+ ///
+ public class PrepareAccessTokenPrincipal : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler()
+ .SetOrder(AttachAuthorization.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Create a new principal containing only the filtered claims.
+ // Actors identities are also filtered (delegation scenarios).
+ var principal = context.Principal.Clone(claim =>
+ {
+ // Never exclude the subject claim.
+ if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ // Always exclude private claims, whose values must generally be kept secret.
+ if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Claims whose destination is not explicitly referenced or doesn't
+ // contain "access_token" are not included in the access token.
+ if (!claim.HasDestination(Destinations.AccessToken))
+ {
+ context.Logger.LogDebug("'{Claim}' was excluded from the access token claims.", claim.Type);
+
+ return false;
+ }
+
+ return true;
+ });
+
+ // Remove the destinations from the claim properties.
+ foreach (var claim in principal.Claims)
+ {
+ claim.Properties.Remove(OpenIddictConstants.Properties.Destinations);
+ }
+
+ // Note: the internal token identifier is automatically reset to ensure
+ // the identifier inherited from the parent token is not automatically reused.
+ principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
+ .SetCreationDate(DateTimeOffset.UtcNow)
+ .SetInternalTokenId(identifier: null);
+
+ var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime;
+ if (lifetime.HasValue)
+ {
+ principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
+ }
+
+ // Set the public audiences collection using the private resource claims stored in the principal.
+ principal.SetAudiences(context.Principal.GetResources());
+
+ // Set the authorized party using the first presenters (typically the client identifier), if available.
+ principal.SetClaim(Claims.AuthorizedParty, context.Principal.GetPresenters().FirstOrDefault());
+
+ // Set the public scope claim using the private scope claims from the principal.
+ // Note: scopes are deliberately formatted as a single space-separated
+ // string to respect the usual representation of the standard scope claim.
+ // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
+ principal.SetClaim(Claims.Scope, string.Join(" ", context.Principal.GetScopes()));
+
+ // When receiving a grant_type=refresh_token request, determine whether the client application
+ // requests a limited set of scopes and immediately replace the scopes collection if necessary.
+ if (context.EndpointType == OpenIddictServerEndpointType.Token &&
+ context.Request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(context.Request.Scope))
+ {
+ var scopes = context.Request.GetScopes();
+ if (scopes.Count != 0)
+ {
+ context.Logger.LogDebug("The access token scopes will be limited to the scopes " +
+ "requested by the client application: {Scopes}.", scopes);
+
+ principal.SetClaim(Claims.Scope, string.Join(" ", scopes.Intersect(context.Principal.GetScopes())));
+ }
+ }
+
+ context.AccessTokenPrincipal = principal;
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of preparing and attaching the claims principal
+ /// used to generate the authorization code, if one is going to be returned.
+ ///
+ public class PrepareAuthorizationCodePrincipal : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler()
+ .SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the internal token identifier is automatically reset to ensure
+ // the identifier inherited from the parent token is not automatically reused.
+ var principal = context.Principal.Clone(_ => true)
+ .SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
+ .SetCreationDate(DateTimeOffset.UtcNow)
+ .SetInternalTokenId(identifier: null);
+
+ var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime;
+ if (lifetime.HasValue)
+ {
+ principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
+ }
+
+ // Attach the redirect_uri to allow for later comparison when
+ // receiving a grant_type=authorization_code token request.
+ if (!string.IsNullOrEmpty(context.Request.RedirectUri))
+ {
+ principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri);
+ }
+
+ // Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier
+ // handler to validate the code verifier sent by the client as part of the token request.
+ if (!string.IsNullOrEmpty(context.Request.CodeChallenge))
+ {
+ principal.SetClaim(Claims.Private.CodeChallenge, context.Request.CodeChallenge);
+
+ // Default to S256 if no explicit code challenge method was specified.
+ principal.SetClaim(Claims.Private.CodeChallengeMethod,
+ !string.IsNullOrEmpty(context.Request.CodeChallengeMethod) ?
+ context.Request.CodeChallengeMethod : CodeChallengeMethods.Sha256);
+ }
+
+ // Attach the nonce so that it can be later returned by
+ // the token endpoint as part of the JWT identity token.
+ if (!string.IsNullOrEmpty(context.Request.Nonce))
+ {
+ principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce);
+ }
+
+ context.AuthorizationCodePrincipal = principal;
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of preparing and attaching the claims principal
+ /// used to generate the refresh token, if one is going to be returned.
+ ///
+ public class PrepareRefreshTokenPrincipal : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler()
+ .SetOrder(PrepareAuthorizationCodePrincipal.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSigninContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the internal token identifier is automatically reset to ensure
+ // the identifier inherited from the parent token is not automatically reused.
+ var principal = context.Principal.Clone(_ => true)
+ .SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
+ .SetCreationDate(DateTimeOffset.UtcNow)
+ .SetInternalTokenId(identifier: null);
+
+ // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed
+ // and must exactly match the expiration date of the refresh token used in the token request.
+ if (context.EndpointType == OpenIddictServerEndpointType.Token &&
+ context.Request.IsRefreshTokenGrantType() && !context.Options.UseSlidingExpiration)
+ {
+ if (!context.Transaction.Properties.TryGetValue(Properties.OriginalPrincipal, out var property))
+ {
+ throw new InvalidOperationException("The original principal cannot be resolved from the transaction.");
+ }
+
+ principal.SetExpirationDate(((ClaimsPrincipal) property).GetExpirationDate());
+ }
+
+ else
+ {
+ var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime;
+ if (lifetime.HasValue)
+ {
+ principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
+ }
+ }
+
+ context.RefreshTokenPrincipal = principal;
+
+ return default;
+ }
+ }
+
+ ///
+ /// Contains the logic responsible of preparing and attaching the claims principal
+ /// used to generate the identity token, if one is going to be returned.
+ ///
+ public class PrepareIdentityTokenPrincipal : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler