Skip to content

Commit fb60e1c

Browse files
authored
Add more system claims and 'sub' support (#1916)
1 parent 504e2a3 commit fb60e1c

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

src/Microsoft.Azure.SignalR.Common/Utilities/ClaimsUtility.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Security.Claims;
88
using Microsoft.AspNetCore.Http.Connections;
99
using Microsoft.Azure.SignalR.Protocol;
10+
using Newtonsoft.Json.Linq;
1011

1112
namespace Microsoft.Azure.SignalR
1213
{
@@ -18,7 +19,14 @@ internal static class ClaimsUtility
1819
"aud", // Audience claim, used by service to make sure token is matched with target resource.
1920
"exp", // Expiration time claims. A token is valid only before its expiration time.
2021
"iat", // Issued At claim. Added by default. It is not validated by service.
21-
"nbf" // Not Before claim. Added by default. It is not validated by service.
22+
"nbf", // Not Before claim. Added by default. It is not validated by service.
23+
"iss", // "iss" is a system claim. It is not validated by service.
24+
"actort",
25+
"acr",
26+
"azp",
27+
"c_hash",
28+
"jti",
29+
"nonce",
2230
};
2331

2432
private static readonly ClaimsIdentity DefaultClaimsIdentity = new ClaimsIdentity();
@@ -115,14 +123,33 @@ public static IEnumerable<Claim> BuildJwtClaims(
115123
}
116124

117125
// return customer's claims
118-
var customerClaims = claimsProvider == null ? user?.Claims : claimsProvider.Invoke();
126+
var customerClaims = (claimsProvider == null ? user?.Claims : claimsProvider.Invoke())?.ToArray();
119127
if (customerClaims != null)
120128
{
129+
// According to the spec https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
130+
// the "sub" value is a case-sensitive string containing a StringOrURI value
131+
// "sub" is used as the UserId if userId is not specified
132+
// If "sub" exists, we here make sure only one "sub" is preserved, others will be renamed as user claim type
133+
var hasSubClaim = false;
121134
foreach (var claim in customerClaims)
122135
{
136+
if (claim.Type == "sub")
137+
{
138+
if (hasSubClaim)
139+
{
140+
// only the first "sub" is preserved as "sub", others will be renamed as user claims
141+
yield return new Claim(Constants.ClaimType.AzureSignalRUserPrefix + claim.Type, claim.Value);
142+
}
143+
else
144+
{
145+
hasSubClaim = true;
146+
// The first "sub" would be also considered as "nameIdentifier" and used as SignalR's UserIdentifier
147+
yield return claim;
148+
}
149+
150+
}
123151
// Add AzureSignalRUserPrefix if customer's claim name is duplicated with SignalR system claims.
124-
// And split it when return from SignalR Service.
125-
if (SystemClaims.Contains(claim.Type))
152+
else if (SystemClaims.Contains(claim.Type))
126153
{
127154
yield return new Claim(Constants.ClaimType.AzureSignalRUserPrefix + claim.Type, claim.Value);
128155
}

test/Microsoft.Azure.SignalR.Common.Tests/ClaimsUtilityTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,80 @@ public void TestGetSystemClaims(ClaimsIdentity identity, string userId, Func<IEn
5050

5151
Assert.Equal(expectedClaimsCount, ci.Claims.Count());
5252
}
53+
54+
[Fact]
55+
public void TestGetPreservedSystemClaims()
56+
{
57+
// preserved system claims are renamed and reverted back
58+
var claims = ClaimsUtility.BuildJwtClaims(
59+
new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("iss", "A"), new Claim("jti", "B") })), null, null).ToArray();
60+
Assert.Equal("asrs.u.iss", claims[0].Type);
61+
Assert.Equal("asrs.u.jti", claims[1].Type);
62+
63+
var resultIdentity = ClaimsUtility.GetUserPrincipal(claims).Identity;
64+
65+
var ci = resultIdentity as ClaimsIdentity;
66+
Assert.NotNull(ci);
67+
Assert.Equal(2, ci.Claims.Count());
68+
Assert.True(ci.HasClaim("iss", "A"));
69+
Assert.True(ci.HasClaim("jti", "B"));
70+
}
71+
72+
[Fact]
73+
public void TestGetSubjectClaims()
74+
{
75+
// only the first sub claim is considered as valid to the service
76+
var claims = ClaimsUtility.BuildJwtClaims(
77+
new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("sub", "A"), new Claim("sub", "B") })), null, null).ToArray();
78+
Assert.Equal("sub", claims[0].Type);
79+
Assert.Equal("asrs.u.sub", claims[1].Type);
80+
81+
var resultIdentity = ClaimsUtility.GetUserPrincipal(claims).Identity;
82+
83+
var ci = resultIdentity as ClaimsIdentity;
84+
Assert.NotNull(ci);
85+
Assert.Equal(2, ci.Claims.Count());
86+
Assert.True(ci.HasClaim("sub", "A"));
87+
Assert.True(ci.HasClaim("sub", "B"));
88+
89+
claims = ClaimsUtility.BuildJwtClaims(
90+
new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("sub", "A"), new Claim("sub", "B") })), "C", null).ToArray();
91+
Assert.Equal("asrs.s.uid", claims[0].Type);
92+
Assert.Equal("sub", claims[1].Type);
93+
Assert.Equal("asrs.u.sub", claims[2].Type);
94+
95+
resultIdentity = ClaimsUtility.GetUserPrincipal(claims).Identity;
96+
97+
ci = resultIdentity as ClaimsIdentity;
98+
Assert.NotNull(ci);
99+
Assert.Equal(2, ci.Claims.Count());
100+
Assert.True(ci.HasClaim("sub", "A"));
101+
Assert.True(ci.HasClaim("sub", "B"));
102+
103+
// single sub claim is considered as valid
104+
claims = ClaimsUtility.BuildJwtClaims(
105+
new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("sub", "A") })), null, null).ToArray();
106+
Assert.Single(claims);
107+
Assert.Equal("sub", claims[0].Type);
108+
109+
resultIdentity = ClaimsUtility.GetUserPrincipal(claims).Identity;
110+
111+
ci = resultIdentity as ClaimsIdentity;
112+
Assert.NotNull(ci);
113+
Assert.Single(ci.Claims);
114+
Assert.True(ci.HasClaim("sub", "A"));
115+
116+
claims = ClaimsUtility.BuildJwtClaims(
117+
new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("sub", "A") })), "C", null).ToArray();
118+
Assert.Equal("asrs.s.uid", claims[0].Type);
119+
Assert.Equal("sub", claims[1].Type);
120+
121+
resultIdentity = ClaimsUtility.GetUserPrincipal(claims).Identity;
122+
123+
ci = resultIdentity as ClaimsIdentity;
124+
Assert.NotNull(ci);
125+
Assert.Single(ci.Claims);
126+
Assert.True(ci.HasClaim("sub", "A"));
127+
}
53128
}
54129
}

0 commit comments

Comments
 (0)