Skip to content

Commit 8e7f07e

Browse files
westin-mbrentschmaltz
authored andcommitted
Add ClaimsMapping to JsonWebTokenHandler
JsonWebTokenHandler now has opt-in claims mapping
1 parent 02518f7 commit 8e7f07e

File tree

7 files changed

+280
-14
lines changed

7 files changed

+280
-14
lines changed

src/System.IdentityModel.Tokens.Jwt/ClaimTypeMapping.cs renamed to src/Microsoft.IdentityModel.JsonWebTokens/ClaimTypeMapping.cs

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

44
using System.Collections.Generic;
55
using System.Security.Claims;
66

7-
namespace System.IdentityModel.Tokens.Jwt
7+
namespace Microsoft.IdentityModel.JsonWebTokens
88
{
9-
/// <summary>
10-
/// Defines the inbound and outbound mapping for claim claim types from jwt to .net claim
11-
/// </summary>
129
internal static class ClaimTypeMapping
1310
{
1411
// This is the short to long mapping.
15-
// key is the long claim type
16-
// value is the short claim type
12+
// key is the long claim type
13+
// value is the short claim type
1714
private static Dictionary<string, string> shortToLongClaimTypeMapping = new Dictionary<string, string>
1815
{
1916
{ JwtRegisteredClaimNames.Actort, ClaimTypes.Actor },

src/Microsoft.IdentityModel.JsonWebTokens/GlobalSuppressions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Previously released as non-static", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(System.String,Microsoft.IdentityModel.Tokens.EncryptingCredentials,System.Collections.Generic.IDictionary{System.String,System.Object})~System.String")]
1313
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Previously released as visible field", Scope = "member", Target = "~F:Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.RegexJws")]
1414
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Previously released as visible field", Scope = "member", Target = "~F:Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.RegexJwe")]
15+
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Breaking change", Scope = "member", Target = "~F:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.DefaultInboundClaimTypeMap")]
16+
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Breaking change", Scope = "member", Target = "~F:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.DefaultMapInboundClaims")]
17+
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Breaking change", Scope = "member", Target = "~F:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.DefaultOutboundClaimTypeMap")]
18+
[assembly: SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Breaking change", Scope = "member", Target = "~P:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.InboundClaimTypeMap")]
19+
[assembly: SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Breaking change", Scope = "member", Target = "~P:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.OutboundClaimTypeMap")]
1520
[assembly: SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "Previously released as non-static/inheritable", Scope = "type", Target = "~T:Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities")]
1621
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as Try method", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryGetPayloadValue``1(System.String,``0@)~System.Boolean")]
1722
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as Try method", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryGetHeaderValue``1(System.String,``0@)~System.Boolean")]

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs

+137
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,39 @@ namespace Microsoft.IdentityModel.JsonWebTokens
2323
/// </summary>
2424
public class JsonWebTokenHandler : TokenHandler
2525
{
26+
private IDictionary<string, string> _inboundClaimTypeMap;
27+
private const string _namespace = "http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties";
28+
private static string _shortClaimType = _namespace + "/ShortTypeName";
29+
private bool _mapInboundClaims = DefaultMapInboundClaims;
30+
31+
/// <summary>
32+
/// Default claim type mapping for inbound claims.
33+
/// </summary>
34+
public static IDictionary<string, string> DefaultInboundClaimTypeMap = new Dictionary<string, string>(ClaimTypeMapping.InboundClaimTypeMap);
35+
36+
/// <summary>
37+
/// Default value for the flag that determines whether or not the InboundClaimTypeMap is used.
38+
/// </summary>
39+
public static bool DefaultMapInboundClaims = false;
40+
2641
/// <summary>
2742
/// Gets the Base64Url encoded string representation of the following JWT header:
2843
/// { <see cref="JwtHeaderParameterNames.Alg"/>, <see cref="SecurityAlgorithms.None"/> }.
2944
/// </summary>
3045
/// <return>The Base64Url encoded string representation of the unsigned JWT header.</return>
3146
public const string Base64UrlEncodedUnsignedJWSHeader = "eyJhbGciOiJub25lIn0";
3247

48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="JsonWebTokenHandler"/> class.
50+
/// </summary>
51+
public JsonWebTokenHandler()
52+
{
53+
if (_mapInboundClaims)
54+
_inboundClaimTypeMap = new Dictionary<string, string>(DefaultInboundClaimTypeMap);
55+
else
56+
_inboundClaimTypeMap = new Dictionary<string, string>();
57+
}
58+
3359
/// <summary>
3460
/// Gets the type of the <see cref="JsonWebToken"/>.
3561
/// </summary>
@@ -39,6 +65,64 @@ public Type TokenType
3965
get { return typeof(JsonWebToken); }
4066
}
4167

68+
/// <summary>
69+
/// Gets or sets the property name of <see cref="Claim.Properties"/> the will contain the original JSON claim 'name' if a mapping occurred when the <see cref="Claim"/>(s) were created.
70+
/// </summary>
71+
/// <exception cref="ArgumentException">If <see cref="string"/>.IsNullOrWhiteSpace('value') is true.</exception>
72+
public static string ShortClaimTypeProperty
73+
{
74+
get
75+
{
76+
return _shortClaimType;
77+
}
78+
79+
set
80+
{
81+
if (string.IsNullOrWhiteSpace(value))
82+
throw LogHelper.LogArgumentNullException(nameof(value));
83+
84+
_shortClaimType = value;
85+
}
86+
}
87+
88+
/// <summary>
89+
/// Gets or sets the <see cref="MapInboundClaims"/> property which is used when determining whether or not to map claim types that are extracted when validating a <see cref="JsonWebToken"/>.
90+
/// <para>If this is set to true, the <see cref="Claim.Type"/> is set to the JSON claim 'name' after translating using this mapping. Otherwise, no mapping occurs.</para>
91+
/// <para>The default value is false.</para>
92+
/// </summary>
93+
public bool MapInboundClaims
94+
{
95+
get
96+
{
97+
return _mapInboundClaims;
98+
}
99+
set
100+
{
101+
if(!_mapInboundClaims && value && _inboundClaimTypeMap.Count == 0)
102+
_inboundClaimTypeMap = new Dictionary<string, string>(DefaultInboundClaimTypeMap);
103+
_mapInboundClaims = value;
104+
}
105+
}
106+
107+
/// <summary>
108+
/// Gets or sets the <see cref="InboundClaimTypeMap"/> which is used when setting the <see cref="Claim.Type"/> for claims in the <see cref="ClaimsPrincipal"/> extracted when validating a <see cref="JsonWebToken"/>.
109+
/// <para>The <see cref="Claim.Type"/> is set to the JSON claim 'name' after translating using this mapping.</para>
110+
/// <para>The default value is ClaimTypeMapping.InboundClaimTypeMap.</para>
111+
/// </summary>
112+
/// <exception cref="ArgumentNullException">'value' is null.</exception>
113+
public IDictionary<string, string> InboundClaimTypeMap
114+
{
115+
get
116+
{
117+
return _inboundClaimTypeMap;
118+
}
119+
120+
set
121+
{
122+
_inboundClaimTypeMap = value ?? throw LogHelper.LogArgumentNullException(nameof(value));
123+
}
124+
}
125+
42126
internal static IDictionary<string, object> AddCtyClaimDefaultValue(IDictionary<string, object> additionalClaims, bool setDefaultCtyClaim)
43127
{
44128
if (!setDefaultCtyClaim)
@@ -680,9 +764,62 @@ protected virtual ClaimsIdentity CreateClaimsIdentity(JsonWebToken jwtToken, Tok
680764
if (string.IsNullOrWhiteSpace(issuer))
681765
issuer = GetActualIssuer(jwtToken);
682766

767+
if (MapInboundClaims)
768+
return CreateClaimsIdentityWithMapping(jwtToken, validationParameters, issuer);
769+
683770
return CreateClaimsIdentityPrivate(jwtToken, validationParameters, issuer);
684771
}
685772

773+
private ClaimsIdentity CreateClaimsIdentityWithMapping(JsonWebToken jwtToken, TokenValidationParameters validationParameters, string issuer)
774+
{
775+
_ = validationParameters ?? throw LogHelper.LogArgumentNullException(nameof(validationParameters));
776+
777+
ClaimsIdentity identity = validationParameters.CreateClaimsIdentity(jwtToken, issuer);
778+
foreach (Claim jwtClaim in jwtToken.Claims)
779+
{
780+
bool wasMapped = _inboundClaimTypeMap.TryGetValue(jwtClaim.Type, out string claimType);
781+
782+
if (!wasMapped)
783+
claimType = jwtClaim.Type;
784+
785+
if (claimType == ClaimTypes.Actor)
786+
{
787+
if (identity.Actor != null)
788+
throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(
789+
LogMessages.IDX14112,
790+
LogHelper.MarkAsNonPII(JwtRegisteredClaimNames.Actort),
791+
jwtClaim.Value)));
792+
793+
if (CanReadToken(jwtClaim.Value))
794+
{
795+
JsonWebToken actor = ReadToken(jwtClaim.Value) as JsonWebToken;
796+
identity.Actor = CreateClaimsIdentity(actor, validationParameters);
797+
}
798+
}
799+
800+
if (wasMapped)
801+
{
802+
Claim claim = new Claim(claimType, jwtClaim.Value, jwtClaim.ValueType, issuer, issuer, identity);
803+
if (jwtClaim.Properties.Count > 0)
804+
{
805+
foreach (var kv in jwtClaim.Properties)
806+
{
807+
claim.Properties[kv.Key] = kv.Value;
808+
}
809+
}
810+
811+
claim.Properties[ShortClaimTypeProperty] = jwtClaim.Type;
812+
identity.AddClaim(claim);
813+
}
814+
else
815+
{
816+
identity.AddClaim(jwtClaim);
817+
}
818+
}
819+
820+
return identity;
821+
}
822+
686823
internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, TokenValidationParameters tokenValidationParameters, string issuer)
687824
{
688825
return CreateClaimsIdentity(securityToken as JsonWebToken, tokenValidationParameters, issuer);

src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
using System.Collections.Generic;
66
using System.Collections.ObjectModel;
77
using System.Globalization;
8-
using System.Linq;
98
using System.Security.Cryptography;
109
using System.Text;
1110
using System.Text.RegularExpressions;
12-
using System.Threading;
1311
using Microsoft.IdentityModel.Json.Linq;
1412
using Microsoft.IdentityModel.Logging;
1513
using Microsoft.IdentityModel.Tokens;
@@ -51,7 +49,7 @@ public class JwtTokenUtilities
5149
/// </summary>
5250
/// <param name="input">String to be signed</param>
5351
/// <param name="signingCredentials">The <see cref="SigningCredentials"/> that contain crypto specs used to sign the token.</param>
54-
/// <returns>The bse64urlendcoded signature over the bytes obtained from UTF8Encoding.GetBytes( 'input' ).</returns>
52+
/// <returns>The base 64 url encoded signature over the bytes obtained from UTF8Encoding.GetBytes( 'input' ).</returns>
5553
/// <exception cref="ArgumentNullException">'input' or 'signingCredentials' is null.</exception>
5654
public static string CreateEncodedSignature(string input, SigningCredentials signingCredentials)
5755
{
@@ -83,7 +81,7 @@ public static string CreateEncodedSignature(string input, SigningCredentials sig
8381
/// <param name="input">String to be signed</param>
8482
/// <param name="signingCredentials">The <see cref="SigningCredentials"/> that contain crypto specs used to sign the token.</param>
8583
/// <param name="cacheProvider">should the <see cref="SignatureProvider"/> be cached.</param>
86-
/// <returns>The bse64urlendcoded signature over the bytes obtained from UTF8Encoding.GetBytes( 'input' ).</returns>
84+
/// <returns>The base 64 url encoded signature over the bytes obtained from UTF8Encoding.GetBytes( 'input' ).</returns>
8785
/// <exception cref="ArgumentNullException"><paramref name="input"/> or <paramref name="signingCredentials"/> is null.</exception>
8886
public static string CreateEncodedSignature(string input, SigningCredentials signingCredentials, bool cacheProvider)
8987
{

src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License.
33

44
using System.Collections.Generic;
5-
using System.Diagnostics;
65
using System.Linq;
76
using System.Runtime.ExceptionServices;
87
using System.Security.Claims;
@@ -39,7 +38,7 @@ public class JwtSecurityTokenHandler : SecurityTokenHandler
3938
/// <summary>
4039
/// Default claim type mapping for inbound claims.
4140
/// </summary>
42-
public static IDictionary<string, string> DefaultInboundClaimTypeMap = ClaimTypeMapping.InboundClaimTypeMap;
41+
public static IDictionary<string, string> DefaultInboundClaimTypeMap = new Dictionary<string, string>(ClaimTypeMapping.InboundClaimTypeMap);
4342

4443
/// <summary>
4544
/// Default value for the flag that determines whether or not the InboundClaimTypeMap is used.
@@ -49,7 +48,7 @@ public class JwtSecurityTokenHandler : SecurityTokenHandler
4948
/// <summary>
5049
/// Default claim type mapping for outbound claims.
5150
/// </summary>
52-
public static IDictionary<string, string> DefaultOutboundClaimTypeMap = ClaimTypeMapping.OutboundClaimTypeMap;
51+
public static IDictionary<string, string> DefaultOutboundClaimTypeMap = new Dictionary<string, string>(ClaimTypeMapping.OutboundClaimTypeMap);
5352

5453
/// <summary>
5554
/// Default claim type filter list.

0 commit comments

Comments
 (0)