in src/Microsoft.Azure.SignalR.Common/Utilities/ClaimsUtility.cs [38:163]
public static IEnumerable<Claim> BuildJwtClaims(
ClaimsPrincipal user,
string userId,
Func<IEnumerable<Claim>> claimsProvider,
string serverName = null,
ServerStickyMode mode = ServerStickyMode.Disabled,
bool enableDetailedErrors = false,
int endpointsCount = 1,
int? maxPollInterval = null,
bool isDiagnosticClient = false, int handshakeTimeout = Constants.Periods.DefaultHandshakeTimeout,
HttpTransportType? httpTransportType = null,
bool closeOnAuthenticationExpiration = false, DateTimeOffset? authenticationExpiresOn = default
)
{
if (userId != null)
{
yield return new Claim(Constants.ClaimType.UserId, userId);
}
if (serverName != null && mode != ServerStickyMode.Disabled)
{
yield return new Claim(Constants.ClaimType.ServerName, serverName);
yield return new Claim(Constants.ClaimType.ServerStickyMode, mode.ToString());
}
if (isDiagnosticClient)
{
yield return new Claim(Constants.ClaimType.DiagnosticClient, "true");
}
if (handshakeTimeout != Constants.Periods.DefaultHandshakeTimeout)
{
yield return new Claim(Constants.ClaimType.CustomHandshakeTimeout, handshakeTimeout.ToString(CultureInfo.InvariantCulture));
}
var authenticationType = user?.Identity?.AuthenticationType;
// No need to pass it when the authentication type is Bearer
if (authenticationType != null && authenticationType != DefaultAuthenticationType)
{
yield return new Claim(Constants.ClaimType.AuthenticationType, authenticationType);
}
// Trace multiple instances
if (endpointsCount > 1)
{
yield return new Claim(Constants.ClaimType.ServiceEndpointsCount, endpointsCount.ToString(CultureInfo.InvariantCulture));
}
if (enableDetailedErrors)
{
yield return new Claim(Constants.ClaimType.EnableDetailedErrors, true.ToString());
}
// Return custom NameClaimType and RoleClaimType
// We can have multiple Identities, for now, choose the default one
if (user?.Identity is ClaimsIdentity identity)
{
var nameType = identity.NameClaimType;
if (nameType != null && nameType != DefaultNameClaimType)
{
yield return new Claim(Constants.ClaimType.NameType, nameType);
}
var roleType = identity.RoleClaimType;
if (roleType != null && roleType != DefaultRoleClaimType)
{
yield return new Claim(Constants.ClaimType.RoleType, roleType);
}
}
// add claim if exists, validation is in DI
if (maxPollInterval.HasValue)
{
yield return new Claim(Constants.ClaimType.MaxPollInterval, maxPollInterval.Value.ToString(CultureInfo.InvariantCulture));
}
if (httpTransportType.HasValue)
{
yield return new Claim(Constants.ClaimType.HttpTransportType, ((int)httpTransportType).ToString(CultureInfo.InvariantCulture));
}
if (closeOnAuthenticationExpiration && authenticationExpiresOn != null && authenticationExpiresOn.HasValue)
{
yield return new Claim(Constants.ClaimType.CloseOnAuthExpiration, "true");
yield return new Claim(Constants.ClaimType.AuthExpiresOn, authenticationExpiresOn.Value.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture));
}
// return customer's claims
var customerClaims = (claimsProvider == null ? user?.Claims : claimsProvider.Invoke())?.ToArray();
if (customerClaims != null)
{
// According to the spec https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
// the "sub" value is a case-sensitive string containing a StringOrURI value
// "sub" is used as the UserId if userId is not specified
// If "sub" exists, we here make sure only one "sub" is preserved, others will be renamed as user claim type
var hasSubClaim = false;
foreach (var claim in customerClaims)
{
if (claim.Type == "sub")
{
if (hasSubClaim)
{
// only the first "sub" is preserved as "sub", others will be renamed as user claims
yield return new Claim(Constants.ClaimType.AzureSignalRUserPrefix + claim.Type, claim.Value);
}
else
{
hasSubClaim = true;
// The first "sub" would be also considered as "nameIdentifier" and used as SignalR's UserIdentifier
yield return claim;
}
}
// Add AzureSignalRUserPrefix if customer's claim name is duplicated with SignalR system claims.
else if (SystemClaims.Contains(claim.Type))
{
yield return new Claim(Constants.ClaimType.AzureSignalRUserPrefix + claim.Type, claim.Value);
}
else
{
yield return claim;
}
}
}
}