Skip to content

Commit

Permalink
Automatically normalize "amr" claims in identity tokens to ensure a J…
Browse files Browse the repository at this point in the history
…SON array is always returned
  • Loading branch information
kevinchalet committed Nov 19, 2024
1 parent c023c9c commit ff752ef
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 5 deletions.
25 changes: 22 additions & 3 deletions src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,8 @@ Claims.Private.CreationDate or Claims.Private.ExpirationDate or
Claims.Private.Scope when context.TokenType is TokenTypeHints.AccessToken => false,
Claims.AuthenticationMethodReference when context.TokenType is TokenTypeHints.IdToken => false,
_ => true
});

Expand All @@ -1398,10 +1400,27 @@ Claims.Private.CreationDate or Claims.Private.ExpirationDate or
var audiences = context.Principal.GetAudiences();
if (audiences.Any())
{
claims.Add(Claims.Audience, audiences.Length switch
claims.Add(Claims.Audience, audiences switch
{
[string audience] => audience,
_ => audiences.ToArray()
});
}
}

// Note: unlike other claims (e.g "aud"), the "amr" claim MUST be represented as a unique
// claim representing a JSON array, even if a single authentication method reference is
// present in the collection. To ensure an array is always returned, the "amr" claim is
// filtered out from the clone principal and manually added as a "string[]" claim value.
if (context.TokenType is TokenTypeHints.IdToken)
{
var methods = context.Principal.GetClaims(Claims.AuthenticationMethodReference);
if (methods.Any())
{
claims.Add(Claims.AuthenticationMethodReference, methods switch
{
1 => audiences.ElementAt(0),
_ => audiences
[string method] => [method],
_ => methods.ToArray()
});
}
}
Expand Down
14 changes: 12 additions & 2 deletions src/OpenIddict.Server/OpenIddictServerHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2216,15 +2216,25 @@ Claims.Private.DeviceCodeId or Claims.Private.ExpirationDate or
=> values is [{ ValueType: ClaimValueTypes.String }],

// The following claims MUST be represented as unique strings or array of strings.
Claims.AuthenticationMethodReference or Claims.Private.Audience or
Claims.Private.Presenter or Claims.Private.Resource
Claims.Private.Audience or Claims.Private.Presenter or Claims.Private.Resource
=> values.TrueForAll(static value => value.ValueType is ClaimValueTypes.String) ||
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),

// Note: unlike other claims (e.g "aud"), the "amr" claim MUST be represented as a unique
// claim representing a JSON array, even if a single authentication method reference
// is present in the collection. To avoid forcing users to use the special JSON_ARRAY
// value type, string values are also allowed here and normalized to JSON arrays
// by OpenIddict when generating an identity token based on the specified principal.
Claims.AuthenticationMethodReference
=> values.TrueForAll(static value => value.ValueType is ClaimValueTypes.String) ||
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),

// The following claims MUST be represented as unique integers.
Claims.Private.AccessTokenLifetime or Claims.Private.AuthorizationCodeLifetime or
Claims.Private.DeviceCodeLifetime or Claims.Private.IdentityTokenLifetime or
Expand Down

0 comments on commit ff752ef

Please sign in to comment.