Browse Source

Use private claims for the token creation/expiration dates and introduce new Data Protection authentication properties

pull/962/head
Kévin Chalet 6 years ago
committed by GitHub
parent
commit
77ca35a53f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  2. 5
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  3. 127
      src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
  4. 3
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs
  5. 35
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs
  6. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  7. 384
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  8. 3
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs
  9. 14
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs
  10. 80
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  11. 209
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
  12. 66
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  13. 16
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
  14. 6
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
  15. 272
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

4
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -191,7 +191,7 @@ namespace Mvc.Server
scopes : principal.GetScopes());
}
principal.SetInternalAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
foreach (var claim in principal.Claims)
{
@ -281,7 +281,7 @@ namespace Mvc.Server
scopes : principal.GetScopes());
}
principal.SetInternalAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
foreach (var claim in principal.Claims)
{

5
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -88,13 +88,16 @@ namespace OpenIddict.Abstractions
public static class Private
{
public const string AccessTokenLifetime = "oi_act_lft";
public const string AuthorizationId = "oi_au_id";
public const string Audience = "oi_aud";
public const string AuthorizationCodeLifetime = "oi_auc_lft";
public const string AuthorizationId = "oi_au_id";
public const string ClaimDestinationsMap = "oi_cl_dstn";
public const string CodeChallenge = "oi_cd_chlg";
public const string CodeChallengeMethod = "oi_cd_chlg_meth";
public const string CreationDate = "oi_crt_dt";
public const string DeviceCodeId = "oi_dvc_id";
public const string DeviceCodeLifetime = "oi_dvc_lft";
public const string ExpirationDate = "oi_exp_dt";
public const string IdentityTokenLifetime = "oi_idt_lft";
public const string Nonce = "oi_nce";
public const string Presenter = "oi_prst";

127
src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs

@ -903,6 +903,27 @@ namespace OpenIddict.Abstractions
return identity.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray();
}
/// <summary>
/// Determines whether the claims identity contains at least one claim of the specified type.
/// </summary>
/// <param name="identity">The claims identity.</param>
/// <param name="type">The claim type.</param>
/// <returns><c>true</c> if the identity contains at least one claim of the specified type.</returns>
public static bool HasClaim([NotNull] this ClaimsIdentity identity, [NotNull] string type)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
return identity.FindAll(type).Any();
}
/// <summary>
/// Gets the claim values corresponding to the given type.
/// </summary>
@ -924,6 +945,27 @@ namespace OpenIddict.Abstractions
return principal.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray();
}
/// <summary>
/// Determines whether the claims principal contains at least one claim of the specified type.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <param name="type">The claim type.</param>
/// <returns><c>true</c> if the principal contains at least one claim of the specified type.</returns>
public static bool HasClaim([NotNull] this ClaimsPrincipal principal, [NotNull] string type)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
return principal.FindAll(type).Any();
}
/// <summary>
/// Removes all the claims corresponding to the given type.
/// </summary>
@ -1113,18 +1155,18 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
var claim = principal.FindFirst(Claims.IssuedAt);
var claim = principal.FindFirst(Claims.Private.CreationDate);
if (claim == null)
{
return null;
}
if (!long.TryParse(claim.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value))
{
return null;
}
return DateTimeOffset.FromUnixTimeSeconds(value);
return value;
}
/// <summary>
@ -1139,18 +1181,18 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
var claim = principal.FindFirst(Claims.ExpiresAt);
var claim = principal.FindFirst(Claims.Private.ExpirationDate);
if (claim == null)
{
return null;
}
if (!long.TryParse(claim.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value))
{
return null;
}
return DateTimeOffset.FromUnixTimeSeconds(value);
return value;
}
/// <summary>
@ -1159,7 +1201,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns>The audiences list or an empty set if the claims cannot be found.</returns>
public static ImmutableArray<string> GetAudiences([NotNull] this ClaimsPrincipal principal)
=> principal.GetClaims(Claims.Audience);
=> principal.GetClaims(Claims.Private.Audience);
/// <summary>
/// Gets the presenters list stored in the claims principal.
@ -1238,7 +1280,7 @@ namespace OpenIddict.Abstractions
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns>The unique identifier or <c>null</c> if the claim cannot be found.</returns>
public static string GetInternalAuthorizationId([NotNull] this ClaimsPrincipal principal)
public static string GetAuthorizationId([NotNull] this ClaimsPrincipal principal)
=> principal.GetClaim(Claims.Private.AuthorizationId);
/// <summary>
@ -1246,7 +1288,7 @@ namespace OpenIddict.Abstractions
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns>The unique identifier or <c>null</c> if the claim cannot be found.</returns>
public static string GetInternalTokenId([NotNull] this ClaimsPrincipal principal)
public static string GetTokenId([NotNull] this ClaimsPrincipal principal)
=> principal.GetClaim(Claims.Private.TokenId);
/// <summary>
@ -1263,14 +1305,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal contains at least one audience.</returns>
public static bool HasAudience([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Audience).Any();
}
=> principal.HasClaim(Claims.Private.Audience);
/// <summary>
/// Determines whether the claims principal contains the given audience.
@ -1290,7 +1325,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The audience cannot be null or empty.", nameof(audience));
}
return principal.HasClaim(Claims.Audience, audience);
return principal.HasClaim(Claims.Private.Audience, audience);
}
/// <summary>
@ -1299,14 +1334,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal contains at least one presenter.</returns>
public static bool HasPresenter([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Presenter).Any();
}
=> principal.HasClaim(Claims.Private.Presenter);
/// <summary>
/// Determines whether the claims principal contains the given presenter.
@ -1335,14 +1363,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal contains at least one resource.</returns>
public static bool HasResource([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Resource).Any();
}
=> principal.HasClaim(Claims.Private.Resource);
/// <summary>
/// Determines whether the claims principal contains the given resource.
@ -1371,14 +1392,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal contains at least one scope.</returns>
public static bool HasScope([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Scope).Any();
}
=> principal.HasClaim(Claims.Private.Scope);
/// <summary>
/// Determines whether the claims principal contains the given scope.
@ -1429,7 +1443,7 @@ namespace OpenIddict.Abstractions
/// <param name="date">The creation date</param>
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetCreationDate([NotNull] this ClaimsPrincipal principal, [CanBeNull] DateTimeOffset? date)
=> SetDateClaim(principal, Claims.IssuedAt, date);
=> principal.SetClaim(Claims.Private.CreationDate, date?.ToString("r", CultureInfo.InvariantCulture));
/// <summary>
/// Sets the expiration date in the claims principal.
@ -1438,7 +1452,7 @@ namespace OpenIddict.Abstractions
/// <param name="date">The expiration date</param>
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetExpirationDate([NotNull] this ClaimsPrincipal principal, [CanBeNull] DateTimeOffset? date)
=> SetDateClaim(principal, Claims.ExpiresAt, date);
=> principal.SetClaim(Claims.Private.ExpirationDate, date?.ToString("r", CultureInfo.InvariantCulture));
/// <summary>
/// Sets the audiences list in the claims principal.
@ -1449,7 +1463,7 @@ namespace OpenIddict.Abstractions
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetAudiences(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] ImmutableArray<string> audiences)
=> principal.SetClaims(Claims.Audience, audiences);
=> principal.SetClaims(Claims.Private.Audience, audiences);
/// <summary>
/// Sets the audiences list in the claims principal.
@ -1632,7 +1646,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <param name="identifier">The unique identifier to store.</param>
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetInternalAuthorizationId([NotNull] this ClaimsPrincipal principal, string identifier)
public static ClaimsPrincipal SetAuthorizationId([NotNull] this ClaimsPrincipal principal, string identifier)
=> principal.SetClaim(Claims.Private.AuthorizationId, identifier);
/// <summary>
@ -1641,7 +1655,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <param name="identifier">The unique identifier to store.</param>
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetInternalTokenId([NotNull] this ClaimsPrincipal principal, string identifier)
public static ClaimsPrincipal SetTokenId([NotNull] this ClaimsPrincipal principal, string identifier)
=> principal.SetClaim(Claims.Private.TokenId, identifier);
/// <summary>
@ -1779,24 +1793,5 @@ namespace OpenIddict.Abstractions
return null;
}
private static ClaimsPrincipal SetDateClaim(ClaimsPrincipal principal, string type, DateTimeOffset? date)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
principal.RemoveClaims(type);
if (date.HasValue)
{
var value = date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture);
var claim = new Claim(type, value, ClaimValueTypes.Integer64);
((ClaimsIdentity)principal.Identity).AddClaim(claim);
}
return principal;
}
}
}

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

@ -16,6 +16,8 @@ namespace OpenIddict.Server.DataProtection
public const string CodeChallenge = ".code_challenge";
public const string CodeChallengeMethod = ".code_challenge_method";
public const string DataProtector = ".data_protector";
public const string DeviceCodeId = ".device_code_id";
public const string DeviceCodeLifetime = ".device_code_lifetime";
public const string Expires = ".expires";
public const string IdentityTokenLifetime = ".identity_token_lifetime";
public const string InternalAuthorizationId = ".internal_authorization_id";
@ -27,6 +29,7 @@ namespace OpenIddict.Server.DataProtection
public const string RefreshTokenLifetime = ".refresh_token_lifetime";
public const string Resources = ".resources";
public const string Scopes = ".scopes";
public const string UserCodeLifetime = ".user_code_lifetime";
}
public static class Purposes

35
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
@ -41,8 +40,6 @@ namespace OpenIddict.Server.DataProtection
return principal
.SetAudiences(GetArrayProperty(properties, Properties.Audiences))
.SetCreationDate(GetDateProperty(properties, Properties.Issued))
.SetExpirationDate(GetDateProperty(properties, Properties.Expires))
.SetPresenters(GetArrayProperty(properties, Properties.Presenters))
.SetResources(GetArrayProperty(properties, Properties.Resources))
.SetScopes(GetArrayProperty(properties, Properties.Scopes))
@ -52,11 +49,16 @@ namespace OpenIddict.Server.DataProtection
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge))
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId))
.SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime))
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId));
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId))
.SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime));
static (ClaimsPrincipal principal, ImmutableDictionary<string, string> properties) Read(BinaryReader reader, int version)
{
@ -177,10 +179,6 @@ namespace OpenIddict.Server.DataProtection
static ImmutableArray<string> GetArrayProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ?
JsonSerializer.Deserialize<ImmutableArray<string>>(value) : ImmutableArray.Create<string>();
static DateTimeOffset? GetDateProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ? (DateTimeOffset?)
DateTimeOffset.ParseExact(value, "r", CultureInfo.InvariantCulture) : null;
}
public void WriteToken([NotNull] BinaryWriter writer, [NotNull] ClaimsPrincipal principal)
@ -201,20 +199,23 @@ namespace OpenIddict.Server.DataProtection
// can't include authentication properties. To ensure tokens can be used with previous versions
// of OpenIddict (1.x/2.x), well-known claims are manually mapped to their properties equivalents.
SetProperty(properties, Properties.Issued, principal.GetCreationDate()?.ToString("r", CultureInfo.InvariantCulture));
SetProperty(properties, Properties.Expires, principal.GetExpirationDate()?.ToString("r", CultureInfo.InvariantCulture));
SetProperty(properties, Properties.Issued, principal.GetClaim(Claims.Private.CreationDate));
SetProperty(properties, Properties.Expires, principal.GetClaim(Claims.Private.ExpirationDate));
SetProperty(properties, Properties.AccessTokenLifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime));
SetProperty(properties, Properties.AuthorizationCodeLifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime));
SetProperty(properties, Properties.DeviceCodeLifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime));
SetProperty(properties, Properties.IdentityTokenLifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime));
SetProperty(properties, Properties.RefreshTokenLifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime));
SetProperty(properties, Properties.UserCodeLifetime, principal.GetClaim(Claims.Private.UserCodeLifetime));
SetProperty(properties, Properties.CodeChallenge, principal.GetClaim(Claims.Private.CodeChallenge));
SetProperty(properties, Properties.CodeChallengeMethod, principal.GetClaim(Claims.Private.CodeChallengeMethod));
SetProperty(properties, Properties.InternalAuthorizationId, principal.GetInternalAuthorizationId());
SetProperty(properties, Properties.InternalTokenId, principal.GetInternalTokenId());
SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId());
SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId());
SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId));
SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce));
SetProperty(properties, Properties.OriginalRedirectUri, principal.GetClaim(Claims.Private.RedirectUri));
@ -226,15 +227,16 @@ namespace OpenIddict.Server.DataProtection
// Copy the principal and exclude the claim that were mapped to authentication properties.
principal = principal.Clone(claim => claim.Type switch
{
Claims.Audience => false,
Claims.ExpiresAt => false,
Claims.IssuedAt => false,
Claims.Private.AccessTokenLifetime => false,
Claims.Private.Audience => false,
Claims.Private.AuthorizationCodeLifetime => false,
Claims.Private.AuthorizationId => false,
Claims.Private.CodeChallenge => false,
Claims.Private.CodeChallengeMethod => false,
Claims.Private.CreationDate => false,
Claims.Private.DeviceCodeId => false,
Claims.Private.DeviceCodeLifetime => false,
Claims.Private.ExpirationDate => false,
Claims.Private.IdentityTokenLifetime => false,
Claims.Private.Nonce => false,
Claims.Private.Presenter => false,
@ -243,6 +245,7 @@ namespace OpenIddict.Server.DataProtection
Claims.Private.Resource => false,
Claims.Private.Scope => false,
Claims.Private.TokenId => false,
Claims.Private.UserCodeLifetime => false,
_ => true
});

2
src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

@ -997,7 +997,7 @@ namespace OpenIddict.Server
}
// Extract the token identifier from the authentication principal.
var identifier = context.Principal.GetInternalTokenId();
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
context.Logger.LogError("The revocation request was rejected because the token had no internal identifier.");

384
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -543,24 +543,73 @@ namespace OpenIddict.Server
return default;
}
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken))
// To reduce the size of tokens, some of the private claims used by OpenIddict
// are mapped to their standard equivalent before being removed from the token.
// This handler is responsible of adding back the private claims to the principal
// when receiving the token (e.g "oi_prst" is resolved from the "scope" claim).
// In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt".
// If the claim doesn't exist, try to infer it from the standard "iat" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.CreationDate))
{
var date = context.Principal.GetClaim(Claims.IssuedAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt".
// If the claim doesn't exist, try to infer it from the standard "exp" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.ExpirationDate))
{
return default;
var date = context.Principal.GetClaim(Claims.ExpiresAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud".
// If no such claim exists, try to infer them from the standard "aud" JWT claims.
if (!context.Principal.HasAudience())
{
var audiences = context.Principal.GetClaims(Claims.Audience);
if (audiences.Any())
{
context.Principal.SetAudiences(audiences);
}
}
// Map the standardized "azp" and "scope" claims to their "oi_" equivalent so that
// the ClaimsPrincipal extensions exposed by OpenIddict return consistent results.
// In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst".
// If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims.
//
// Note: in previous OpenIddict versions, the presenters were represented in JWT tokens
// using the "azp" claim (defined by OpenID Connect), for which a single value could be
// specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x
// can still be read with OpenIddict 3.0, the presenter is automatically inferred from
// the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal.
if (!context.Principal.HasPresenter())
{
context.Principal.SetPresenters(context.Principal.GetClaims(Claims.AuthorizedParty));
var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ??
context.Principal.GetClaim(Claims.ClientId);
if (!string.IsNullOrEmpty(presenter))
{
context.Principal.SetPresenters(presenter);
}
}
// In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp".
//
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
// is formatted as a unique space-separated string containing all the granted scopes.
// To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-03 for more information.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information.
if (!context.Principal.HasScope())
{
var scopes = context.Principal.GetClaims(Claims.Scope);
@ -569,7 +618,10 @@ namespace OpenIddict.Server
scopes = scopes[0].Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray();
}
context.Principal.SetScopes(scopes);
if (scopes.Any())
{
context.Principal.SetScopes(scopes);
}
}
return default;
@ -634,8 +686,8 @@ namespace OpenIddict.Server
context.Principal = context.Principal
.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetInternalTokenId(await _tokenManager.GetIdAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
@ -771,7 +823,7 @@ namespace OpenIddict.Server
// 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();
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
@ -816,7 +868,7 @@ namespace OpenIddict.Server
await _tokenManager.TryRevokeAsync(token);
// Then, try to revoke the authorization and the associated token entries.
await TryRevokeAuthorizationChainAsync(context.Principal.GetInternalAuthorizationId());
await TryRevokeAuthorizationChainAsync(context.Principal.GetAuthorizationId());
context.Logger.LogError("The token '{Identifier}' has already been redeemed.", identifier);
@ -897,8 +949,8 @@ namespace OpenIddict.Server
// 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))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
async ValueTask TryRevokeAuthorizationChainAsync(string identifier)
@ -919,7 +971,7 @@ namespace OpenIddict.Server
await foreach (var token in _tokenManager.FindByAuthorizationIdAsync(identifier))
{
// Don't change the status of the token used in the token request.
if (string.Equals(context.Principal.GetInternalTokenId(),
if (string.Equals(context.Principal.GetTokenId(),
await _tokenManager.GetIdAsync(token), StringComparison.Ordinal))
{
continue;
@ -969,7 +1021,7 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
var identifier = context.Principal.GetInternalAuthorizationId();
var identifier = context.Principal.GetAuthorizationId();
if (string.IsNullOrEmpty(identifier))
{
return;
@ -1286,7 +1338,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("The authentication context cannot be found.");
// Extract the device code identifier from the authentication principal.
var identifier = notification.Principal.GetInternalTokenId();
var identifier = notification.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException("The token identifier cannot be extracted from the principal.");
@ -1741,7 +1793,7 @@ namespace OpenIddict.Server
}
// If an authorization identifier was explicitly specified, don't create an ad-hoc authorization.
if (!string.IsNullOrEmpty(context.Principal.GetInternalAuthorizationId()))
if (!string.IsNullOrEmpty(context.Principal.GetAuthorizationId()))
{
return;
}
@ -1791,7 +1843,7 @@ namespace OpenIddict.Server
// 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);
context.Principal.SetAuthorizationId(identifier);
}
}
@ -1893,10 +1945,11 @@ namespace OpenIddict.Server
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
}
// Set the public audiences collection using the private resource claims stored in the principal.
// Set the audiences based on the resource claims stored in the principal.
principal.SetAudiences(context.Principal.GetResources());
// Store the client_id as a public client_id claim, if available.
// Store the client identifier in the public client_id claim, if available.
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information.
principal.SetClaim(Claims.ClientId, context.ClientId);
// When receiving a grant_type=refresh_token request, determine whether the client application
@ -2271,6 +2324,10 @@ namespace OpenIddict.Server
principal.SetAudiences(context.ClientId);
}
// Use the client_id as the authorized party, if available.
// See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information.
principal.SetClaim(Claims.AuthorizedParty, context.ClientId);
// If a nonce was present in the authorization request, it MUST be included in the id_token generated
// by the token endpoint. For that, OpenIddict simply flows the nonce as an authorization code claim.
// See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information.
@ -2278,6 +2335,7 @@ namespace OpenIddict.Server
{
OpenIddictServerEndpointType.Authorization => context.Request.Nonce,
OpenIddictServerEndpointType.Token => context.Principal.GetClaim(Claims.Private.Nonce),
_ => null
});
@ -2428,7 +2486,7 @@ namespace OpenIddict.Server
// 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.
var identifier = context.Principal.GetInternalTokenId();
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
@ -2517,7 +2575,7 @@ namespace OpenIddict.Server
// If the operation fails, silently ignore the error and keep processing the request:
// this may indicate that one of the revoked tokens was modified by a concurrent request.
var identifier = context.Principal.GetInternalAuthorizationId();
var identifier = context.Principal.GetAuthorizationId();
if (string.IsNullOrEmpty(identifier))
{
return;
@ -2526,7 +2584,7 @@ namespace OpenIddict.Server
await foreach (var token in _tokenManager.FindByAuthorizationIdAsync(identifier))
{
// Don't change the status of the token used in the token request.
if (string.Equals(context.Principal.GetInternalTokenId(),
if (string.Equals(context.Principal.GetTokenId(),
await _tokenManager.GetIdAsync(token), StringComparison.Ordinal))
{
continue;
@ -2590,7 +2648,7 @@ namespace OpenIddict.Server
// 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.
var identifier = context.Principal.GetInternalTokenId();
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
@ -2670,12 +2728,12 @@ namespace OpenIddict.Server
var principal = context.AccessTokenPrincipal;
if (principal == null)
{
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
throw new InvalidOperationException("A token cannot be created from a null principal.");
}
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = principal.GetInternalAuthorizationId(),
AuthorizationId = principal.GetAuthorizationId(),
CreationDate = principal.GetCreationDate(),
ExpirationDate = principal.GetExpirationDate(),
Principal = principal,
@ -2705,7 +2763,7 @@ namespace OpenIddict.Server
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
context.Logger.LogTrace("The token entry for access token '{Identifier}' was successfully created.", identifier);
}
@ -2746,55 +2804,78 @@ namespace OpenIddict.Server
return default;
}
// Copy the principal and exclude the presenters/scopes private claims,
// that are manually mapped to public standard azp/scope JWT claims.
var principal = context.AccessTokenPrincipal.Clone(claim => claim.Type switch
// Clone the principal and exclude the private claims mapped to standard JWT claims.
var principal = context.AccessTokenPrincipal?.Clone(claim => claim.Type switch
{
Claims.Private.Presenter => false,
Claims.Private.Scope => false,
Claims.Private.Audience => false,
Claims.Private.CreationDate => false,
Claims.Private.ExpirationDate => false,
Claims.Private.Scope => false,
Claims.Private.TokenType => false,
_ => true
});
// Set the authorized party using the first presenter (typically the client identifier), if available.
principal.SetClaim(Claims.AuthorizedParty, context.AccessTokenPrincipal.GetPresenters().FirstOrDefault());
if (principal == null)
{
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var claims = new Dictionary<string, object>(StringComparer.Ordinal);
// Set the public audience claims using the private audience claims from the principal.
// Note: when there's a single audience, represent it as a unique string claim.
var audiences = context.AccessTokenPrincipal.GetAudiences();
if (audiences.Any())
{
claims.Add(Claims.Audience, audiences.Length switch
{
1 => audiences.ElementAt(0),
_ => audiences
});
}
// 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.AccessTokenPrincipal.GetScopes()));
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04.
var scopes = context.AccessTokenPrincipal.GetScopes();
if (scopes.Any())
{
claims.Add(Claims.Scope, string.Join(" ", scopes));
}
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
var descriptor = new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AccessToken
},
Claims = claims,
Expires = context.AccessTokenPrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.AccessTokenPrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) principal.Identity
});
};
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
if (!context.Options.DisableAccessTokenEncryption)
{
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.FirstOrDefault(
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AccessToken
});
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
}
context.Response.AccessToken = token;
context.Logger.LogTrace("The access token '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.AccessTokenPrincipal.GetClaim(Claims.JwtId),
context.Response.AccessToken, principal.Claims);
principal.GetClaim(Claims.JwtId), token, principal.Claims);
return default;
}
@ -2857,7 +2938,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var identifier = principal.GetInternalTokenId();
var identifier = principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException("The token identifier cannot be extracted from the principal.");
@ -2954,7 +3035,7 @@ namespace OpenIddict.Server
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = principal.GetInternalAuthorizationId(),
AuthorizationId = principal.GetAuthorizationId(),
CreationDate = principal.GetCreationDate(),
ExpirationDate = principal.GetExpirationDate(),
Principal = principal,
@ -2984,7 +3065,7 @@ namespace OpenIddict.Server
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
context.Logger.LogTrace("The token entry for authorization code '{Identifier}' was successfully created.", identifier);
}
@ -3025,20 +3106,37 @@ namespace OpenIddict.Server
return default;
}
// Clone the principal and exclude the claim mapped to standard JWT claims.
var principal = context.AuthorizationCodePrincipal?.Clone(claim => claim.Type switch
{
Claims.Private.CreationDate => false,
Claims.Private.ExpirationDate => false,
Claims.Private.TokenType => false,
_ => true
});
if (principal == null)
{
throw new InvalidOperationException("A token cannot be created from a null principal.");
}
var descriptor = new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.AuthorizationCode
},
Expires = context.AuthorizationCodePrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.AuthorizationCodePrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.AuthorizationCodePrincipal.Identity
Subject = (ClaimsIdentity) principal.Identity
};
// Attach claims destinations to the JWT claims collection.
var destinations = context.AuthorizationCodePrincipal.GetDestinations();
var destinations = principal.GetDestinations();
if (destinations.Count != 0)
{
descriptor.Claims = new Dictionary<string, object>(StringComparer.Ordinal)
@ -3049,21 +3147,18 @@ namespace OpenIddict.Server
// Sign and encrypt the authorization code.
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.FirstOrDefault(
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.AuthorizationCode
});
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.Code = token;
context.Logger.LogTrace("The authorization code '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.AuthorizationCodePrincipal.GetClaim(Claims.JwtId), token,
context.AuthorizationCodePrincipal.Claims);
principal.GetClaim(Claims.JwtId), token, principal.Claims);
return default;
}
@ -3126,7 +3221,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var identifier = principal.GetInternalTokenId();
var identifier = principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException("The token identifier cannot be extracted from the principal.");
@ -3228,7 +3323,7 @@ namespace OpenIddict.Server
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = principal.GetInternalAuthorizationId(),
AuthorizationId = principal.GetAuthorizationId(),
CreationDate = principal.GetCreationDate(),
ExpirationDate = principal.GetExpirationDate(),
Principal = principal,
@ -3258,7 +3353,7 @@ namespace OpenIddict.Server
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
context.Logger.LogTrace("The token entry for device code '{Identifier}' was successfully created.", identifier);
}
@ -3299,20 +3394,37 @@ namespace OpenIddict.Server
return default;
}
// Clone the principal and exclude the claim mapped to standard JWT claims.
var principal = context.DeviceCodePrincipal?.Clone(claim => claim.Type switch
{
Claims.Private.CreationDate => false,
Claims.Private.ExpirationDate => false,
Claims.Private.TokenType => false,
_ => true
});
if (principal == null)
{
throw new InvalidOperationException("A token cannot be created from a null principal.");
}
var descriptor = new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.DeviceCode
},
Expires = context.DeviceCodePrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.DeviceCodePrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.DeviceCodePrincipal.Identity
Subject = (ClaimsIdentity) principal.Identity
};
// Attach claims destinations to the JWT claims collection.
var destinations = context.DeviceCodePrincipal.GetDestinations();
var destinations = principal.GetDestinations();
if (destinations.Count != 0)
{
descriptor.Claims = new Dictionary<string, object>(StringComparer.Ordinal)
@ -3323,21 +3435,18 @@ namespace OpenIddict.Server
// Sign and encrypt the device code.
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.FirstOrDefault(
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.DeviceCode
});
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.DeviceCode = token;
context.Logger.LogTrace("The device code '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.DeviceCodePrincipal.GetClaim(Claims.JwtId), token,
context.DeviceCodePrincipal.Claims);
principal.GetClaim(Claims.JwtId), token, principal.Claims);
return default;
}
@ -3405,7 +3514,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var identifier = principal.GetInternalTokenId();
var identifier = principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException("The token identifier cannot be extracted from the principal.");
@ -3598,7 +3707,7 @@ namespace OpenIddict.Server
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = principal.GetInternalAuthorizationId(),
AuthorizationId = principal.GetAuthorizationId(),
CreationDate = principal.GetCreationDate(),
ExpirationDate = principal.GetExpirationDate(),
Principal = principal,
@ -3628,7 +3737,7 @@ namespace OpenIddict.Server
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
context.Logger.LogTrace("The token entry for refresh token '{Identifier}' was successfully created.", identifier);
}
@ -3669,20 +3778,37 @@ namespace OpenIddict.Server
return default;
}
// Clone the principal and exclude the claim mapped to standard JWT claims.
var principal = context.RefreshTokenPrincipal?.Clone(claim => claim.Type switch
{
Claims.Private.CreationDate => false,
Claims.Private.ExpirationDate => false,
Claims.Private.TokenType => false,
_ => true
});
if (principal == null)
{
throw new InvalidOperationException("A token cannot be created from a null principal.");
}
var descriptor = new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.RefreshToken
},
Expires = context.RefreshTokenPrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.RefreshTokenPrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.RefreshTokenPrincipal.Identity
Subject = (ClaimsIdentity) principal.Identity
};
// Attach claims destinations to the JWT claims collection.
var destinations = context.RefreshTokenPrincipal.GetDestinations();
var destinations = principal.GetDestinations();
if (destinations.Count != 0)
{
descriptor.Claims = new Dictionary<string, object>(StringComparer.Ordinal)
@ -3693,21 +3819,18 @@ namespace OpenIddict.Server
// Sign and encrypt the refresh token.
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.FirstOrDefault(
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.RefreshToken
});
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.RefreshToken = token;
context.Logger.LogTrace("The refresh token '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.RefreshTokenPrincipal.GetClaim(Claims.JwtId), token,
context.RefreshTokenPrincipal.Claims);
principal.GetClaim(Claims.JwtId), token, principal.Claims);
return default;
}
@ -3770,7 +3893,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var identifier = principal.GetInternalTokenId();
var identifier = principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException("The token identifier cannot be extracted from the principal.");
@ -3844,7 +3967,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var identifier = context.DeviceCodePrincipal.GetInternalTokenId();
var identifier = context.DeviceCodePrincipal.GetTokenId();
if (!string.IsNullOrEmpty(identifier))
{
principal.SetClaim(Claims.Private.DeviceCodeId, identifier);
@ -3913,7 +4036,7 @@ namespace OpenIddict.Server
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = principal.GetInternalAuthorizationId(),
AuthorizationId = principal.GetAuthorizationId(),
CreationDate = principal.GetCreationDate(),
ExpirationDate = principal.GetExpirationDate(),
Principal = principal,
@ -3943,7 +4066,7 @@ namespace OpenIddict.Server
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
context.Logger.LogTrace("The token entry for user code '{Identifier}' was successfully created.", identifier);
}
@ -3984,34 +4107,49 @@ namespace OpenIddict.Server
return default;
}
// Sign and encrypt the user code.
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
// Clone the principal and exclude the claim mapped to standard JWT claims.
var principal = context.UserCodePrincipal?.Clone(claim => claim.Type switch
{
Claims.Private.CreationDate => false,
Claims.Private.ExpirationDate => false,
Claims.Private.TokenType => false,
_ => true
});
if (principal == null)
{
throw new InvalidOperationException("A token cannot be created from a null principal.");
}
var descriptor = new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.UserCode
},
Expires = context.UserCodePrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.UserCodePrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.UserCodePrincipal.Identity
});
Subject = (ClaimsIdentity) principal.Identity
};
// Sign and encrypt the user code.
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.FirstOrDefault(
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.UserCode
});
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.UserCode = token;
context.Logger.LogTrace("The user code '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.UserCodePrincipal.GetClaim(Claims.JwtId), token,
context.UserCodePrincipal.Claims);
principal.GetClaim(Claims.JwtId), token, principal.Claims);
return default;
}
@ -4074,7 +4212,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("A token entry cannot be created from a null principal.");
}
var identifier = principal.GetInternalTokenId();
var identifier = principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException("The token identifier cannot be extracted from the principal.");
@ -4315,7 +4453,7 @@ namespace OpenIddict.Server
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = principal.GetInternalAuthorizationId(),
AuthorizationId = principal.GetAuthorizationId(),
CreationDate = principal.GetCreationDate(),
ExpirationDate = principal.GetExpirationDate(),
Principal = principal,
@ -4345,7 +4483,7 @@ namespace OpenIddict.Server
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
context.Logger.LogTrace("The token entry for identity token '{Identifier}' was successfully created.", identifier);
}
@ -4386,23 +4524,59 @@ namespace OpenIddict.Server
return default;
}
// Sign and attach the identity token.
context.Response.IdToken = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
// Clone the principal and exclude the claim mapped to standard JWT claims.
var principal = context.IdentityTokenPrincipal?.Clone(claim => claim.Type switch
{
Claims.Private.Audience => false,
Claims.Private.CreationDate => false,
Claims.Private.ExpirationDate => false,
Claims.Private.TokenType => false,
_ => true
});
if (principal == null)
{
throw new InvalidOperationException("A token cannot be created from a null principal.");
}
var claims = new Dictionary<string, object>(StringComparer.Ordinal);
// Set the public audience claims using the private audience claims from the principal.
// Note: when there's a single audience, represent it as a unique string claim.
var audiences = context.IdentityTokenPrincipal.GetAudiences();
if (audiences.Any())
{
claims.Add(Claims.Audience, audiences.Length switch
{
1 => audiences.ElementAt(0),
_ => audiences
});
}
var descriptor = new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.IdentityToken
},
Claims = claims,
Expires = context.IdentityTokenPrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.IdentityTokenPrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(credentials =>
credentials.Key is AsymmetricSecurityKey),
Subject = (ClaimsIdentity) context.IdentityTokenPrincipal.Identity
});
Subject = (ClaimsIdentity) principal.Identity
};
// Sign and attach the identity token.
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
context.Response.IdToken = token;
context.Logger.LogTrace("The identity token '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.IdentityTokenPrincipal.GetClaim(Claims.JwtId),
context.Response.IdToken, context.IdentityTokenPrincipal.Claims);
principal.GetClaim(Claims.JwtId), token, principal.Claims);
return default;
}

3
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs

@ -16,6 +16,8 @@ namespace OpenIddict.Validation.DataProtection
public const string CodeChallenge = ".code_challenge";
public const string CodeChallengeMethod = ".code_challenge_method";
public const string DataProtector = ".data_protector";
public const string DeviceCodeId = ".device_code_id";
public const string DeviceCodeLifetime = ".device_code_lifetime";
public const string Expires = ".expires";
public const string IdentityTokenLifetime = ".identity_token_lifetime";
public const string InternalAuthorizationId = ".internal_authorization_id";
@ -27,6 +29,7 @@ namespace OpenIddict.Validation.DataProtection
public const string RefreshTokenLifetime = ".refresh_token_lifetime";
public const string Resources = ".resources";
public const string Scopes = ".scopes";
public const string UserCodeLifetime = ".user_code_lifetime";
}
public static class Purposes

14
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Security.Claims;
using System.Text.Json;
@ -39,8 +38,6 @@ namespace OpenIddict.Validation.DataProtection
return principal
.SetAudiences(GetArrayProperty(properties, Properties.Audiences))
.SetCreationDate(GetDateProperty(properties, Properties.Issued))
.SetExpirationDate(GetDateProperty(properties, Properties.Expires))
.SetPresenters(GetArrayProperty(properties, Properties.Presenters))
.SetResources(GetArrayProperty(properties, Properties.Resources))
.SetScopes(GetArrayProperty(properties, Properties.Scopes))
@ -50,11 +47,16 @@ namespace OpenIddict.Validation.DataProtection
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge))
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId))
.SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime))
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId));
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId))
.SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime));
static (ClaimsPrincipal principal, ImmutableDictionary<string, string> properties) Read(BinaryReader reader, int version)
{
@ -175,10 +177,6 @@ namespace OpenIddict.Validation.DataProtection
static ImmutableArray<string> GetArrayProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ?
JsonSerializer.Deserialize<ImmutableArray<string>>(value) : ImmutableArray.Create<string>();
static DateTimeOffset? GetDateProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ? (DateTimeOffset?)
DateTimeOffset.ParseExact(value, "r", CultureInfo.InvariantCulture) : null;
}
}
}

80
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text;
@ -382,19 +383,73 @@ namespace OpenIddict.Validation
return default;
}
// Map the standardized "azp" and "scope" claims to their "oi_" equivalent so that
// the ClaimsPrincipal extensions exposed by OpenIddict return consistent results.
// To reduce the size of tokens, some of the private claims used by OpenIddict
// are mapped to their standard equivalent before being removed from the token.
// This handler is responsible of adding back the private claims to the principal
// when receiving the token (e.g "oi_prst" is resolved from the "scope" claim).
// In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt".
// If the claim doesn't exist, try to infer it from the standard "iat" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.CreationDate))
{
var date = context.Principal.GetClaim(Claims.IssuedAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt".
// If the claim doesn't exist, try to infer it from the standard "exp" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.ExpirationDate))
{
var date = context.Principal.GetClaim(Claims.ExpiresAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud".
// If no such claim exists, try to infer them from the standard "aud" JWT claims.
if (!context.Principal.HasAudience())
{
var audiences = context.Principal.GetClaims(Claims.Audience);
if (audiences.Any())
{
context.Principal.SetAudiences(audiences);
}
}
// In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst".
// If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims.
//
// Note: in previous OpenIddict versions, the presenters were represented in JWT tokens
// using the "azp" claim (defined by OpenID Connect), for which a single value could be
// specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x
// can still be read with OpenIddict 3.0, the presenter is automatically inferred from
// the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal.
if (!context.Principal.HasPresenter())
{
context.Principal.SetPresenters(context.Principal.GetClaims(Claims.AuthorizedParty));
var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ??
context.Principal.GetClaim(Claims.ClientId);
if (!string.IsNullOrEmpty(presenter))
{
context.Principal.SetPresenters(presenter);
}
}
// In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp".
//
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
// is formatted as a unique space-separated string containing all the granted scopes.
// To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-03 for more information.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information.
if (!context.Principal.HasScope())
{
var scopes = context.Principal.GetClaims(Claims.Scope);
@ -403,7 +458,10 @@ namespace OpenIddict.Validation
scopes = scopes[0].Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray();
}
context.Principal.SetScopes(scopes);
if (scopes.Any())
{
context.Principal.SetScopes(scopes);
}
}
return default;
@ -465,8 +523,8 @@ namespace OpenIddict.Validation
context.Principal = context.Principal
.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetInternalTokenId(await _tokenManager.GetIdAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
@ -682,7 +740,7 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(context));
}
var identifier = context.Principal.GetInternalTokenId();
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
@ -703,8 +761,8 @@ namespace OpenIddict.Validation
// 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))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
@ -745,7 +803,7 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(context));
}
var identifier = context.Principal.GetInternalAuthorizationId();
var identifier = context.Principal.GetAuthorizationId();
if (string.IsNullOrEmpty(identifier))
{
return;

209
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs

@ -1497,7 +1497,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetClaim_ReturnsNullForMissingClaim()
{
// Arrange
var principal = new ClaimsPrincipal();
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
// Act and assert
Assert.Null(principal.GetClaim("type"));
@ -1531,33 +1532,26 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetCreationDate_ReturnsNullIfNoClaim()
{
// Arrange
var principal = new ClaimsPrincipal();
// Act
var creationDate = principal.GetCreationDate();
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
// Assert
Assert.Null(creationDate);
// Act and assert
Assert.Null(principal.GetCreationDate());
}
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData(" ", null)]
[InlineData("62", "62")]
[InlineData("bad_data", null)]
public void GetCreationDate_ReturnsCreationDate(string issuedAt, string expected)
[Fact]
public void GetCreationDate_ReturnsCreationDate()
{
// Arrange
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaim(Claims.IssuedAt, issuedAt);
principal.SetClaim(Claims.Private.CreationDate, "Wed, 01 Jan 2020 04:30:30 GMT");
// Act
var creationDate = principal.GetCreationDate();
var date = principal.GetCreationDate();
// Assert
Assert.Equal(ParseDateTimeOffset(expected), creationDate);
Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date);
}
[Fact]
@ -1578,31 +1572,23 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Arrange
var principal = new ClaimsPrincipal();
// Act
var expirationDate = principal.GetExpirationDate();
// Assert
Assert.Null(expirationDate);
// Act and assert
Assert.Null(principal.GetExpirationDate());
}
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData(" ", null)]
[InlineData("62", "62")]
[InlineData("bad_data", null)]
public void GetExpirationDate_ReturnsExpirationDate(string expiresAt, string expected)
[Fact]
public void GetExpirationDate_ReturnsExpirationDate()
{
// Arrange
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaim(Claims.ExpiresAt, expiresAt);
principal.SetClaim(Claims.Private.ExpirationDate, "Wed, 01 Jan 2020 04:30:30 GMT");
// Act
var expirationDate = principal.GetExpirationDate();
var date = principal.GetExpirationDate();
// Assert
Assert.Equal(ParseDateTimeOffset(expected), expirationDate);
Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date);
}
[Fact]
@ -1629,7 +1615,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Audience, audience.ToImmutableArray());
principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray());
// Act and assert
Assert.Equal(audiences, principal.GetAudiences());
@ -1888,13 +1874,13 @@ namespace OpenIddict.Abstractions.Tests.Primitives
}
[Fact]
public void GetInternalAuthorizationId_ThrowsAnExceptionForNullPrincipal()
public void GetAuthorizationId_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.GetInternalAuthorizationId());
var exception = Assert.Throws<ArgumentNullException>(() => principal.GetAuthorizationId());
Assert.Equal("principal", exception.ParamName);
}
@ -1902,7 +1888,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[Theory]
[InlineData(null)]
[InlineData("identifier")]
public void GetInternalAuthorizationId_ReturnsExpectedResult(string identifier)
public void GetAuthorizationId_ReturnsExpectedResult(string identifier)
{
// Arrange
var identity = new ClaimsIdentity();
@ -1911,17 +1897,17 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetClaim(Claims.Private.AuthorizationId, identifier);
// Act and assert
Assert.Equal(identifier, principal.GetInternalAuthorizationId());
Assert.Equal(identifier, principal.GetAuthorizationId());
}
[Fact]
public void GetInternalTokenId_ThrowsAnExceptionForNullPrincipal()
public void GetTokenId_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.GetInternalTokenId());
var exception = Assert.Throws<ArgumentNullException>(() => principal.GetTokenId());
Assert.Equal("principal", exception.ParamName);
}
@ -1929,7 +1915,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[Theory]
[InlineData(null)]
[InlineData("identifier")]
public void GetInternalTokenId_ReturnsExpectedResult(string identifier)
public void GetTokenId_ReturnsExpectedResult(string identifier)
{
// Arrange
var identity = new ClaimsIdentity();
@ -1938,7 +1924,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetClaim(Claims.Private.TokenId, identifier);
// Act and assert
Assert.Equal(identifier, principal.GetInternalTokenId());
Assert.Equal(identifier, principal.GetTokenId());
}
[Fact]
@ -2004,7 +1990,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Audience, audience.ToImmutableArray());
principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasAudience());
@ -2026,7 +2012,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Audience, audience.ToImmutableArray());
principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasAudience("fabrikam"));
@ -2294,6 +2280,92 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.Equal("value", identity.GetClaim("type"));
}
[Fact]
public void GetClaims_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.GetClaims("type"));
Assert.Equal("principal", exception.ParamName);
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type)
{
// Arrange
var principal = new ClaimsPrincipal();
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => principal.GetClaims(type));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith("The claim type cannot be null or empty.", exception.Message);
}
[Fact]
public void GetClaims_ReturnsExpectedResult()
{
// Arrange
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur"));
identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId));
identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile));
var principal = new ClaimsPrincipal(identity);
// Act and assert
Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, principal.GetClaims(Claims.Scope));
}
[Fact]
public void HasClaim_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.HasClaim("type"));
Assert.Equal("principal", exception.ParamName);
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void HasClaim_ThrowsAnExceptionForNullOrEmptyClaimType(string type)
{
// Arrange
var principal = new ClaimsPrincipal();
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => principal.HasClaim(type));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith("The claim type cannot be null or empty.", exception.Message);
}
[Fact]
public void HasClaim_ReturnsExpectedResult()
{
// Arrange
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur"));
identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId));
identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile));
var principal = new ClaimsPrincipal(identity);
// Act and assert
Assert.True(principal.HasClaim(Claims.Name));
Assert.True(principal.HasClaim(Claims.Scope));
Assert.False(principal.HasClaim(Claims.Nickname));
}
[Fact]
public void RemoveClaims_ThrowsAnExceptionForNullPrincipal()
{
@ -2413,25 +2485,23 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetCreationDate(default(DateTimeOffset)));
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetCreationDate(date: null));
Assert.Equal("principal", exception.ParamName);
}
[Theory]
[InlineData(null)]
[InlineData("62")]
public void SetCreationDate_AddsIssuedAtClaim(string date)
[Fact]
public void SetCreationDate_AddsClaim()
{
// Arrange
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
// Act
principal.SetCreationDate(ParseDateTimeOffset(date));
principal.SetCreationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)));
// Assert
Assert.Equal(date, principal.GetClaim(Claims.IssuedAt));
Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.CreationDate));
}
[Fact]
@ -2441,25 +2511,23 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetExpirationDate(default(DateTimeOffset)));
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetExpirationDate(date: null));
Assert.Equal("principal", exception.ParamName);
}
[Theory]
[InlineData(null)]
[InlineData("62")]
public void SetExpirationDate_AddsExpiresAtClaim(string date)
[Fact]
public void SetExpirationDate_AddsClaim()
{
// Arrange
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
// Act
principal.SetExpirationDate(ParseDateTimeOffset(date));
principal.SetExpirationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)));
// Assert
Assert.Equal(date, principal.GetClaim(Claims.ExpiresAt));
Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.ExpirationDate));
}
[Fact]
@ -2491,7 +2559,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetAudiences(audiences);
// Assert
Assert.Equal(audience, principal.GetClaims(Claims.Audience));
Assert.Equal(audience, principal.GetClaims(Claims.Private.Audience));
}
[Fact]
@ -2799,13 +2867,13 @@ namespace OpenIddict.Abstractions.Tests.Primitives
}
[Fact]
public void SetInternalAuthorizationId_ThrowsAnExceptionForNullPrincipal()
public void SetAuthorizationId_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetInternalAuthorizationId(null));
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetAuthorizationId(null));
Assert.Equal("principal", exception.ParamName);
}
@ -2813,27 +2881,27 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[Theory]
[InlineData(null)]
[InlineData("identifier")]
public void SetInternalAuthorizationId_AddsScopes(string identifier)
public void SetAuthorizationId_AddsScopes(string identifier)
{
// Arrange
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
// Act
principal.SetInternalAuthorizationId(identifier);
principal.SetAuthorizationId(identifier);
// Assert
Assert.Equal(identifier, principal.GetClaim(Claims.Private.AuthorizationId));
}
[Fact]
public void SetInternalTokenId_ThrowsAnExceptionForNullPrincipal()
public void SetTokenId_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetInternalTokenId(null));
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetTokenId(null));
Assert.Equal("principal", exception.ParamName);
}
@ -2841,14 +2909,14 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[Theory]
[InlineData(null)]
[InlineData("identifier")]
public void SetInternalTokenId_AddsScopes(string identifier)
public void SetTokenId_AddsScopes(string identifier)
{
// Arrange
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
// Act
principal.SetInternalTokenId(identifier);
principal.SetTokenId(identifier);
// Assert
Assert.Equal(identifier, principal.GetClaim(Claims.Private.TokenId));
@ -2890,14 +2958,5 @@ namespace OpenIddict.Abstractions.Tests.Primitives
return lifeT;
}
private DateTimeOffset? ParseDateTimeOffset(string dateTimeOffset)
{
var dtOffset = string.IsNullOrWhiteSpace(dateTimeOffset)
? null
: (DateTimeOffset?)DateTimeOffset.FromUnixTimeSeconds(long.Parse(dateTimeOffset, NumberStyles.Number, CultureInfo.InvariantCulture));
return dtOffset;
}
}
}

66
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs

@ -1669,7 +1669,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -1733,7 +1733,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
.SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256);
@ -1885,7 +1885,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -1947,7 +1947,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2002,7 +2002,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2072,7 +2072,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Fabrikam")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2137,7 +2137,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2215,7 +2215,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2275,8 +2275,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2368,8 +2368,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2470,8 +2470,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2568,8 +2568,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Fabrikam")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2641,8 +2641,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2725,8 +2725,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Fabrikam")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2784,8 +2784,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2883,8 +2883,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2965,8 +2965,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3061,8 +3061,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3152,8 +3152,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3234,8 +3234,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3332,7 +3332,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(context.TokenType)
.SetPresenters("Fabrikam")
.SetInternalTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC")
.SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
if (context.Request.IsAuthorizationCodeGrantType())

16
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs

@ -1219,8 +1219,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetAudiences("Fabrikam")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique");
@ -1310,8 +1310,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetAudiences("Fabrikam")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique");
@ -1408,8 +1408,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetAudiences("Fabrikam")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique");
@ -1519,8 +1519,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetAudiences("Fabrikam")
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique");

6
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs

@ -674,7 +674,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
return default;
});
@ -732,7 +732,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
return default;
});
@ -793,7 +793,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
return default;
});

272
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

@ -217,6 +217,106 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
}
[Fact]
public async Task ProcessAuthentication_IssuedAtIsMappedToCreationDate()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
var identity = new ClaimsIdentity("Bearer");
identity.AddClaim(new Claim(Claims.IssuedAt, "1577836800", ClaimValueTypes.Integer64));
context.Principal = new ClaimsPrincipal(identity)
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal(1577836800, (long) response[Claims.IssuedAt]);
Assert.Equal("Wed, 01 Jan 2020 00:00:00 GMT", (string) response[Claims.Private.CreationDate]);
}
[Fact]
public async Task ProcessAuthentication_ExpiresAtIsMappedToExpirationDate()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
var identity = new ClaimsIdentity("Bearer");
identity.AddClaim(new Claim(Claims.ExpiresAt, "2524608000", ClaimValueTypes.Integer64));
context.Principal = new ClaimsPrincipal(identity)
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal(2524608000, (long) response[Claims.ExpiresAt]);
Assert.Equal("Sat, 01 Jan 2050 00:00:00 GMT", (string) response[Claims.Private.ExpirationDate]);
}
[Fact]
public async Task ProcessAuthentication_AuthorizedPartyIsMappedToPresenter()
{
@ -265,6 +365,150 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal("Fabrikam", (string) response[Claims.Private.Presenter]);
}
[Fact]
public async Task ProcessAuthentication_ClientIdIsMappedToPresenter()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique")
.SetClaim(Claims.ClientId, "Fabrikam");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal("Fabrikam", (string) response[Claims.ClientId]);
Assert.Equal("Fabrikam", (string) response[Claims.Private.Presenter]);
}
[Fact]
public async Task ProcessAuthentication_SinglePublicAudienceIsMappedToPrivateClaims()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique")
.SetClaim(Claims.Audience, "Fabrikam");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal("Fabrikam", (string) response[Claims.Audience]);
Assert.Equal("Fabrikam", (string) response[Claims.Private.Audience]);
}
[Fact]
public async Task ProcessAuthentication_MultiplePublicAudiencesAreMappedToPrivateClaims()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique")
.SetClaims(Claims.Audience, ImmutableArray.Create("Fabrikam", "Contoso"));
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]) response[Claims.Audience]);
Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]) response[Claims.Private.Audience]);
}
[Fact]
public async Task ProcessAuthentication_SinglePublicScopeIsMappedToPrivateClaims()
{
@ -2666,7 +2910,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2736,7 +2980,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2821,7 +3065,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2886,7 +3130,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -2946,7 +3190,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3030,8 +3274,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3120,8 +3364,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3197,7 +3441,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3258,7 +3502,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3320,7 +3564,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3386,7 +3630,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
@ -3450,7 +3694,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;

Loading…
Cancel
Save