diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index c467b571..30ec5114 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1575,6 +1575,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
A configuration manager must be attached to the client registration to be able to resolve the server configuration.
+
+ Multiple claims of the same type are present in the identity or principal.
+
The security token is missing.
diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
index 31f90040..033a7bc1 100644
--- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
+++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
@@ -1462,10 +1462,13 @@ public static class OpenIddictExtensions
}
///
- /// Gets the claim value corresponding to the given type.
+ /// Gets the unique claim value corresponding to the given type.
///
/// The identity.
/// The type associated with the claim.
+ ///
+ /// Multiple claims using the same are present in the identity.
+ ///
/// The claim value.
public static string? GetClaim(this ClaimsIdentity identity, string type)
{
@@ -1479,14 +1482,43 @@ public static class OpenIddictExtensions
throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type));
}
- return identity.FindFirst(type)?.Value;
+ var claims = identity.FindAll(type);
+ if (claims is IList list)
+ {
+ return list switch
+ {
+ [Claim claim] => claim.Value,
+ [ ] => null,
+ [ .. ] => throw new InvalidOperationException(SR.GetResourceString(SR.ID0423))
+ };
+ }
+
+ else
+ {
+ using var enumerator = claims.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ var claim = enumerator.Current;
+ if (!enumerator.MoveNext())
+ {
+ return claim.Value;
+ }
+
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0423));
+ }
+
+ return null;
+ }
}
///
- /// Gets the claim value corresponding to the given type.
+ /// Gets the unique claim value corresponding to the given type.
///
/// The principal.
/// The type associated with the claim.
+ ///
+ /// Multiple claims using the same are present in the principal.
+ ///
/// The claim value.
public static string? GetClaim(this ClaimsPrincipal principal, string type)
{
@@ -1500,11 +1532,37 @@ public static class OpenIddictExtensions
throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type));
}
- return principal.FindFirst(type)?.Value;
+ var claims = principal.FindAll(type);
+ if (claims is IList list)
+ {
+ return list switch
+ {
+ [Claim claim] => claim.Value,
+ [ ] => null,
+ [ .. ] => throw new InvalidOperationException(SR.GetResourceString(SR.ID0423))
+ };
+ }
+
+ else
+ {
+ using var enumerator = claims.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ var claim = enumerator.Current;
+ if (!enumerator.MoveNext())
+ {
+ return claim.Value;
+ }
+
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0423));
+ }
+
+ return null;
+ }
}
///
- /// Gets the claim values corresponding to the given type.
+ /// Gets all the claim values corresponding to the given type.
///
/// The claims identity.
/// The type associated with the claims.
@@ -1525,7 +1583,7 @@ public static class OpenIddictExtensions
}
///
- /// Gets the claim values corresponding to the given type.
+ /// Gets all the claim values corresponding to the given type.
///
/// The claims principal.
/// The type associated with the claims.
diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
index 87d5cc08..c5eb9127 100644
--- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
@@ -2412,6 +2412,40 @@ public class OpenIddictExtensionsTests
Assert.Equal("principal", exception.ParamName);
}
+ [Fact]
+ public void ClaimsIdentity_GetClaim_ThrowsAnExceptionForDuplicateClaimType()
+ {
+ // Arrange
+ var identity = new ClaimsIdentity();
+ identity.AddClaim(Claims.Name, "Bob le Bricoleur");
+ identity.AddClaim(Claims.Name, "Bob le Bricoleur");
+
+ // Act and assert
+ var exception = Assert.Throws(() =>
+ {
+ identity.GetClaim(Claims.Name);
+ });
+
+ Assert.Equal(SR.GetResourceString(SR.ID0423), exception.Message);
+ }
+
+ [Fact]
+ public void ClaimsPrincipal_GetClaim_ThrowsAnExceptionForDuplicateClaimType()
+ {
+ // Arrange
+ var principal = new ClaimsPrincipal(new ClaimsIdentity());
+ principal.AddClaim(Claims.Name, "Bob le Bricoleur");
+ principal.AddClaim(Claims.Name, "Bob le Bricoleur");
+
+ // Act and assert
+ var exception = Assert.Throws(() =>
+ {
+ principal.GetClaim(Claims.Name);
+ });
+
+ Assert.Equal(SR.GetResourceString(SR.ID0423), exception.Message);
+ }
+
[Fact]
public void ClaimsIdentity_GetClaim_ReturnsNullForMissingClaims()
{