Skip to content

Commit

Permalink
Add SecurityTokenClaimsIdentity
Browse files Browse the repository at this point in the history
  • Loading branch information
pmaytak committed Dec 20, 2024
1 parent 04739db commit bf53634
Show file tree
Hide file tree
Showing 12 changed files with 516 additions and 16 deletions.
168 changes: 168 additions & 0 deletions benchmark/Microsoft.IdentityModel.Benchmarks/ClaimsIdentityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

namespace Microsoft.IdentityModel.Benchmarks
{
// dotnet run -c release -f net8.0 --filter Microsoft.IdentityModel.Benchmarks.ClaimsIdentityTests*

[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class ClaimsIdentityTests
{
private ClaimsIdentity _claimsIdentity;
private SecurityTokenClaimsIdentity _newClaimsIdentity;
private string _claimTypeToFind;
private string _claimValueToFind;
private Predicate<Claim> _findPredicate;
private Predicate<Claim> _hasClaimPredicate;

private JsonWebTokenHandler _jsonWebTokenHandler;
private string _jwsWithExtendedClaims;
private TokenValidationParameters _tokenValidationParameters;
private TokenValidationParameters _newTokenValidationParameters;

[GlobalSetup]
public async Task SetupAsync()
{
_jsonWebTokenHandler = new JsonWebTokenHandler();
_jwsWithExtendedClaims = _jsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
{
Claims = BenchmarkUtils.ClaimsExtendedExample,
SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256,
});
_tokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = BenchmarkUtils.Audience,
ValidateLifetime = true,
ValidIssuer = BenchmarkUtils.Issuer,
IssuerSigningKey = BenchmarkUtils.SigningCredentialsRsaSha256.Key,
};
_newTokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = BenchmarkUtils.Audience,
ValidateLifetime = true,
ValidIssuer = BenchmarkUtils.Issuer,
IssuerSigningKey = BenchmarkUtils.SigningCredentialsRsaSha256.Key,
UseNewClaimsIdentityType = true,
};

_claimTypeToFind = "iss";
_claimValueToFind = BenchmarkUtils.Issuer;
_findPredicate = claim => claim.Type == _claimTypeToFind;
_hasClaimPredicate = claim => claim.Type == _claimTypeToFind && claim.Value == _claimValueToFind;

_claimsIdentity = (await _jsonWebTokenHandler.ValidateTokenAsync(_jwsWithExtendedClaims, _tokenValidationParameters).ConfigureAwait(false)).ClaimsIdentity;
_newClaimsIdentity = (await _jsonWebTokenHandler.ValidateTokenAsync(_jwsWithExtendedClaims, _newTokenValidationParameters).ConfigureAwait(false)).ClaimsIdentity as SecurityTokenClaimsIdentity;
_ = _claimsIdentity.Claims;
_ = _newClaimsIdentity.Claims;
}

[Benchmark(Baseline = true), BenchmarkCategory("FindFirst")]
public Claim ClaimsIdentity_FindFirst()
{
var temp = _claimsIdentity.FindFirst(_claimTypeToFind);
return temp;
}

//[Benchmark(Baseline = true), BenchmarkCategory("FindFirstPredicate")]
public Claim ClaimsIdentity_FindFirst_WithPredicate()
{
var temp = _claimsIdentity.FindFirst(_findPredicate);
return temp;
}

[Benchmark(Baseline = true), BenchmarkCategory("FindAll")]
public List<Claim> ClaimsIdentity_FindAll()
{
var temp = _claimsIdentity.FindAll(_claimTypeToFind).ToList();
return temp;
}

//[Benchmark(Baseline = true), BenchmarkCategory("FindAllPredicate")]
public List<Claim> ClaimsIdentity_FindAll_WithPredicate()
{
var temp = _claimsIdentity.FindAll(_findPredicate).ToList();
return temp;
}

[Benchmark(Baseline = true), BenchmarkCategory("HasPayloadClaim")]
public bool ClaimsIdentity_HasClaim()
{
var temp = _claimsIdentity.HasClaim(_claimTypeToFind, _claimValueToFind);
return temp;
}

//[Benchmark(Baseline = true), BenchmarkCategory("HasClaimPredicate")]
public bool ClaimsIdentity_HasClaim_WithPredicate()
{
var temp = _claimsIdentity.HasClaim(_hasClaimPredicate);
return temp;
}

[Benchmark, BenchmarkCategory("FindFirst")]
public Claim NewClaimsIdentity_FindFirst()
{
var temp = _newClaimsIdentity.FindFirst(_claimTypeToFind);
return temp;
}

//[Benchmark, BenchmarkCategory("FindFirstPredicate")]
public Claim NewClaimsIdentity_FindFirst_WithPredicate()
{
var temp = _newClaimsIdentity.FindFirst(_findPredicate);
return temp;
}

[Benchmark, BenchmarkCategory("FindAll")]
public List<Claim> NewClaimsIdentity_FindAll()
{
var temp = _newClaimsIdentity.FindAll(_claimTypeToFind).ToList();
return temp;
}

//[Benchmark, BenchmarkCategory("FindAllPredicate")]
public List<Claim> NewClaimsIdentity_FindAll_WithPredicate()
{
var temp = _newClaimsIdentity.FindAll(_findPredicate).ToList();
return temp;
}

[Benchmark, BenchmarkCategory("HasPayloadClaim")]
public bool NewClaimsIdentity_HasClaim()
{
var temp = _newClaimsIdentity.HasClaim(_claimTypeToFind, _claimValueToFind);
return temp;
}

//[Benchmark, BenchmarkCategory("HasClaimPredicate")]
public bool NewClaimsIdentity_HasClaim_WithPredicate()
{
var temp = _newClaimsIdentity.HasClaim(_hasClaimPredicate);
return temp;
}

//[Benchmark(Baseline = true), BenchmarkCategory("ValidateAndGetClaims")]
public async Task<IList<Claim>> ClaimsIdentity_ValidateTokenAndGetClaims()
{
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsWithExtendedClaims, _tokenValidationParameters).ConfigureAwait(false);
var claimsIdentity = result.ClaimsIdentity;
var claims = claimsIdentity.Claims;
return claims.ToList();
}

//[Benchmark, BenchmarkCategory("ValidateAndGetClaims")]
public async Task<IList<Claim>> NewClaimsIdentity_ValidateTokenAndGetClaims()
{
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsWithExtendedClaims, _newTokenValidationParameters).ConfigureAwait(false);
var claimsIdentity = result.ClaimsIdentity;
var claims = claimsIdentity.Claims;
return claims.ToList();
}
}
}
9 changes: 9 additions & 0 deletions benchmark/Microsoft.IdentityModel.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ public static void Main(string[] args)
}
private static void DebugThroughTests()
{
ClaimsIdentityTests claimsIdentityTests = new ClaimsIdentityTests();
claimsIdentityTests.SetupAsync().GetAwaiter().GetResult();
var claim = claimsIdentityTests.ClaimsIdentity_FindFirst();
var claimsList = claimsIdentityTests.ClaimsIdentity_FindAll();
var hasClaim = claimsIdentityTests.ClaimsIdentity_HasClaim();
claim = claimsIdentityTests.NewClaimsIdentity_FindFirst();
claimsList = claimsIdentityTests.NewClaimsIdentity_FindAll();
hasClaim = claimsIdentityTests.NewClaimsIdentity_HasClaim();

ReadJWETokenTests readTokenTests = new ReadJWETokenTests();
readTokenTests.Setup();
readTokenTests.ReadJWE_FromMemory();
Expand Down
5 changes: 3 additions & 2 deletions build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@
<PropertyGroup>
<NoWarn>$(NoWarn);SYSLIB0050</NoWarn>
<NoWarn>$(NoWarn);SYSLIB0051</NoWarn>
<NoWarn>$(NoWarn);RS0016;RS0017;RS0051</NoWarn>
</PropertyGroup>

<ItemGroup>
<!-- Protects against sync-over-async: https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md. -->
<!-- Protects against sync-over-async: https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md. -->
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="$(MicrosoftSourceLinkGitHubVersion)" PrivateAssets="All"/>
</ItemGroup>
Expand Down Expand Up @@ -78,5 +79,5 @@
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

</Project>
1 change: 1 addition & 0 deletions build/commonTest.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<NoWarn>$(NoWarn);SYSLIB0050</NoWarn>
<NoWarn>$(NoWarn);SYSLIB0051</NoWarn>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn);RS0016;RS0017;RS0051</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
80 changes: 78 additions & 2 deletions src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,85 @@ internal bool TryGetValue<T>(string key, out T value)
return found;
}

internal bool HasClaim(string claimName)
internal Claim GetPayloadClaim(string name, string issuer)
{
return _jsonClaims.TryGetValue(claimName, out _);
if (_jsonClaims.TryGetValue(name, out object val))
{
Claim claim = CreateClaimFromObject(name, val, issuer);
if (claim != null)
return claim;
}

return null;
}

internal bool HasPayloadClaim(string name)
{
return _jsonClaims.TryGetValue(name, out _);
}

internal bool HasPayloadClaim(string name, string value, string issuer)
{
if (_jsonClaims.TryGetValue(name, out object val))
{
Claim claim = CreateClaimFromObject(name, val, issuer);
if (claim != null)
return claim?.Value.Equals(value, StringComparison.Ordinal) == true;
}

return false;
}

internal static Claim CreateClaimFromObject(string claimType, object value, string issuer)
{
// Json.net recognized DateTime by default.
if (value is string str)
return new Claim(claimType, str, JwtTokenUtilities.GetStringClaimValueType(str, claimType), issuer, issuer);
else if (value is int i)
return new Claim(claimType, i.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer32, issuer, issuer);
else if (value is long l)
return new Claim(claimType, l.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64, issuer, issuer);
else if (value is bool b)
{
// Can't just use ToString or bools will get encoded as True/False instead of true/false.
if (b)
return new Claim(claimType, "true", ClaimValueTypes.Boolean, issuer, issuer);
else
return new Claim(claimType, "false", ClaimValueTypes.Boolean, issuer, issuer);
}
else if (value is double d)
return new Claim(claimType, d.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer);
else if (value is DateTime dt)
return new Claim(claimType, dt.ToString("o", CultureInfo.InvariantCulture), ClaimValueTypes.DateTime, issuer, issuer);
else if (value is float f)
return new Claim(claimType, f.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer);
else if (value is decimal m)
return new Claim(claimType, m.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer);
else if (value is null)
return new Claim(claimType, string.Empty, JsonClaimValueTypes.JsonNull, issuer, issuer);
else if (value is IList ilist)
{
foreach (var item in ilist)
return CreateClaimFromObject(claimType, item, issuer);
}
else if (value is JsonElement j)
if (j.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement jsonElement in j.EnumerateArray())
{
Claim claim = CreateClaimFromJsonElement(claimType, issuer, jsonElement);
if (claim != null)
return claim;
}
}
else
{
Claim claim = CreateClaimFromJsonElement(claimType, issuer, j);
if (claim != null)
return claim;
}

return null;
}
}
}
25 changes: 19 additions & 6 deletions src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.IdentityModel.JsonWebTokens
/// <summary>
/// A <see cref="SecurityToken"/> designed for representing a JSON Web Token (JWT).
/// </summary>
public partial class JsonWebToken : SecurityToken
public partial class JsonWebToken : ClaimsProvider
{
internal const string ClassName = "Microsoft.IdentityModel.JsonWebTokens.JsonWebToken";

Expand Down Expand Up @@ -624,6 +624,24 @@ public Claim GetClaim(string key)
return Payload.GetClaim(key, Issuer ?? ClaimsIdentity.DefaultIssuer);
}

/// <inheritdoc/>
public override Claim GetPayloadClaim(string name)
{
return Payload.GetPayloadClaim(name, Issuer ?? ClaimsIdentity.DefaultIssuer);
}

/// <inheritdoc/>
public override bool HasPayloadClaim(string name, string value)
{
return Payload.HasPayloadClaim(name, value, Issuer ?? ClaimsIdentity.DefaultIssuer);
}

/// <inheritdoc/>
public override bool HasPayloadClaim(string type)
{
return Payload.HasPayloadClaim(type);
}

/// <summary>
/// Gets the names of the payload claims on the JsonWebToken.
/// </summary>
Expand Down Expand Up @@ -700,11 +718,6 @@ public bool TryGetClaim(string key, out Claim value)
#endregion

#region Get Claims from the JWT Header and Payload
internal bool HasPayloadClaim(string claimName)
{
return Payload.HasClaim(claimName);
}

/// <summary>
/// Gets the 'value' corresponding to key from the JWT header transformed as type 'T'.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class CaseSensitiveClaimsIdentity : ClaimsIdentity
/// <summary>
/// Gets the <see cref="SecurityToken"/> associated with this claims identity.
/// </summary>
[field: NonSerialized]
public SecurityToken SecurityToken { get; internal set; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,25 @@ internal static ClaimsIdentity Create(IEnumerable<Claim> claims, string authenti
return new CaseSensitiveClaimsIdentity(claims, authenticationType);
}

internal static ClaimsIdentity Create(string authenticationType, string nameType, string roleType, SecurityToken securityToken)
internal static ClaimsIdentity Create(string authenticationType, string nameType, string roleType, SecurityToken securityToken, TokenValidationParameters tokenValidationParameters)
{
if (AppContextSwitches.UseClaimsIdentityType)
return new ClaimsIdentity(authenticationType: authenticationType, nameType: nameType, roleType: roleType);

return new CaseSensitiveClaimsIdentity(authenticationType: authenticationType, nameType: nameType, roleType: roleType)
if (tokenValidationParameters.UseNewClaimsIdentityType)
{
SecurityToken = securityToken,
};
return new SecurityTokenClaimsIdentity(authenticationType: authenticationType, nameType: nameType, roleType: roleType)
{
SecurityToken = securityToken,
};
}
else
{
return new CaseSensitiveClaimsIdentity(authenticationType: authenticationType, nameType: nameType, roleType: roleType)
{
SecurityToken = securityToken,
};
}
}
}

Expand Down
Loading

0 comments on commit bf53634

Please sign in to comment.