Recently, I was looking for a way to sign id tokens in Azure AD B2C using certificates. I found that we have to use custom policy to do this.
Here is how to do it.
Upload a pfx file (Ex. B2C_1A_signing) to "Policy Keys" through "Identity Experience Framework".
Override the token issuer claims provider in your relying party policy. If you are using Active Directory B2C Custom Policy Starterpack, you can overwrite TrustFrameworkBase.xml by default.
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_signing" />
</CryptographicKeys>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Notice that the issuer_secret is changed to reference the storage reference id of the certificate.
Check metadata
{
"keys": [
{"kid":"E62C82DD3E3CED17DFE23D0FFB837E46C5B36182","exp":4652954830,"nbf":1497280630,"key_ops":["sign"],"x5c":["<Certificate>"}
]
}
Kid references the thumbprint of the cert and the cert is listed as x5c.
Send an authentication request.
{
"typ": "JWT",
"alg": "RS256",
"kid": "E62C82DD3E3CED17DFE23D0FFB837E46C5B36182",
"x5t": "5iyC3T487Rff4j0P-4N-RsWzYYI"
}.{
"exp": 1505922183,
"nbf": 1505918583,
"ver": "1.0",
"iss": "https://login.microsoftonline.com/<>/v2.0/",
"sub": "<>",
"aud": "<>",
"acr": "b2c_1a_signup_signin",
"nonce": "defaultNonce",
"iat": 1505918583,
"auth_time": 1505918583,
"name": "Chi Yao"
}.[Signature]
Related
I'm trying to add a custom policy without refresh token
I've modifiy the ClaimsProviders this way :
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
<Item Key="AuthenticationContextReferenceClaimPattern">None</Item>
<Item Key="token_lifetime_secs">3600</Item>
<!-- 1 H -->
<Item Key="id_token_lifetime_secs">3600</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
But it's not working.
I'm facing this js error when I try to sign in :
main.js:1 ERROR ServerError: server_error: AADB2C: Issuer technical profile 'JwtIssuer' must specify a 'issuer_refresh_token_user_identity_claim_type' to use this OAuth flow
Correlation ID: bab26044-1e53-4b4a-b5c9-d2f35030a9d7
Any ideas ?
Thanks :)
Thanks for the comment #paralight. We need to apply some workaround to achieve the same as there is no way to remove the refresh token and no direct modifications to the JWTIssuer technical profile.
Workaround:
Force a session time out by adding a tag UserJourneyBehaviors in custom policy. ex :
<UserJourneyBehaviors> <SingleSignOn Scope="Application" /> <SessionExpiryType>Absolute</SessionExpiryType> <SessionExpiryInSeconds>900</SessionExpiryInSeconds> </UserJourneyBehaviors>
Other scenario :
Claims information in the JWT token is exposed to the public. To store some sensitive information in the JWT token
Workaround: You can send claims to a REST API and send them back to B2C to encrypt.
You would create an orchestration step before the SendClaims step to send all claims to a REST API, and have the REST API respond with encrypted versions of those claims
JWT Token issuer reference https://learn.microsoft.com/en-us/azure/active-directory-b2c/jwt-issuer-technical-profile
REST API usage https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-rest-api-claims-exchange
Reference : All Technical Profiles : https://learn.microsoft.com/en-us/azure/active-directory-b2c/technicalprofiles
Reference SO Thread: Modify the JwtIssuer ClaimsProvider in the custom policy to achieve the JWE in AD B2C
Adding your comment as answer and glad to know your queries are addressed. You can accept it as answer( click on the check mark beside the answer to toggle it from greyed out to filled in). This can be beneficial to other community members.
I have an id_token_hint with a token from an https://sts.windows.net/. I'm willing to use it as a trusted party based on the role present in the token. Here's a JWT token example:
{
"iss": "https://sts.windows.net/00000000-0000-0000-0000-000000000000/",
"iat": 1610050840,
"nbf": 1610050840,
"exp": 1610054740,
"aio": "E2JgYPi646//0000000000000000000=",
"app_displayname": "my_app_displayname",
"appid": "00000000-0000-0000-0000-000000000000",
"appidacr": "1",
"idp": "https://sts.windows.net/00000000-0000-0000-0000-000000000000/",
"idtyp": "app",
"oid": "00000000-73e4-46ae-b464-000000000000",
"rh": "0.AAAAiKphxJIQoUmmKLTdWWDB80kfo3ST3nNJgG0000000000000.",
"roles": [
"Mail.Send",
"Policy.ReadWrite.TrustFramework"
],
"sub": "00000000-73e4-46ae-b464-000000000000",
"tenant_region_scope": "EU",
"tid": "00000000-0000-0000-0000-000000000000",
"uti": "jDVAsZtcd0ezvvkFN00000",
"ver": "1.0",
"xms_tcdt": 1599800000
}
As you can see here I have a field roles, which contains an array of roles. I'm trying to parse this array into specific claims like IsPolicyReadWriteRole and IsMailSendRole. However, I cannot find any examples over the starter pack or examples how to do that. I don't need these claims to be themself, I just plan to use them as a precondition in the orchestration step.
You can extract claims from id_token_hint using the instructions and sample mentioned here - https://learn.microsoft.com/en-us/azure/active-directory-b2c/id-token-hint
Then you can use the getsingleitemfromstringcollection claim transformation to get the claim value.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/stringcollection-transformations#getsingleitemfromstringcollection
And then you can use claimExist or claimEquals predicate to test for the value.
The id_token_hint validation technical profile will look like below
<ClaimsProvider>
<DisplayName>Trustframework Policy Engine TechnicalProfiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TpEngine_IdTokenHint_ExtractClaims">
<DisplayName>Trustframework Policy Engine ID Token Hint Setup Technical Profile</DisplayName>
<Protocol Name="None" />
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I'm using Graph to query a user profile in Azure B2C. I'm able to query the users, but I don't see the Source field to determine the Source of Authority. What field is this?
I'm currently using the .28-preview of the Microsoft.Graph.Beta NuGet package.
And this is what I see in the debugger under Identities:
How would I tell the difference if that was a Google account or an Azure AD account?
Using Microsoft Graph, it’s the issuerId field within the Identities array and only returns on beta version.
Source is not included in the identities array, and is also not included in the properties.
As this issue with PowerShell shows, onPremisesSyncEnabled property will help.
I solved this by creating a custom attribute and then in the custom policies setting the custom attribute based on signup method (see alternative solution near the end).
How to define custom attributes and use them with the MS Graph API and custom policies is explained pretty well here. The hardest part is perhaps getting the custom policy right. I did everything in TrustFrameworkExtensions.xml. First defining an "extension_authoritySource" ClaimType:
<ClaimType Id="extension_AuthoritySource">
<DisplayName>AuthoritySource</DisplayName>
<DataType>string</DataType>
</ClaimType>
Then in <TechnicalProfile Id="Facebook-OAUTH"> I added an OutputClaim which sets this custom attribute to facebook, but this will only be persisted if a PersistedClaim is made in UserWriteUsingAlternativeSecurityId as shown below:
<OutputClaim ClaimTypeReferenceId="extension_AuthoritySource" DefaultValue="Facebook"/>
To persist the custom attribute I added the following to ClaimsProviders:
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<Item Key="ClientId">[b2c-extensions-app application ID]</Item>
<Item Key="ApplicationObjectId">[b2c-extensions-app application ObjectId]</Item>
</Metadata>
</TechnicalProfile>
<!-- Write data during a local account sign-up flow. -->
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="extension_AuthoritySource" DefaultValue="local"/>
</PersistedClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWriteUsingAlternativeSecurityId">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="extension_AuthoritySource" DefaultValue="social"/>
</PersistedClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Note that with the above email signups will always be set as "local", while UserWriteUsingAlternativeSecurityId sets it as "social", but is overwritten by the output claim from facebook.
My thinking here is that UserWriteUsingLogonEmail is only ever used by email signup, whereas UserWriteUsingAlternativeSecurityId could potentially be used by several federated logins, although at the moment I only use facebook.
Alternative without Custom Attribute
Alternatively, if you are not using custom policies or cannot use the approach above for another reason, you can use the MS Graph API and look in the "identities" array which contains the sign in type. So for a given user GET: https://graph.microsoft.com/v1.0/users/[Users objectID Guid]?$select=identities
In this array you can find for a local signup:
{
"signInType": "emailAddress",
"issuer": "[yourdomain].onmicrosoft.com",
"issuerAssignedId": "[email]"
}
and for facebook:
{
"signInType": "federated",
"issuer": "facebook.com",
"issuerAssignedId": "[number]"
}
Every user also has a "userPrincipalName" item in the identities array so you will have to have some logic to loop through the array and only look for the signInType which you want to support. Yet another reason for preferring using custom attribute and setting the authority source yourself.
I just followed the tutorial on the page and created a certificate and exposed it to the azure portal.
I also uploaded the policy files and modified them with my tenant.
I'm running the application on my localhost, but when I wanna browse to that link, I got following error in jwt.ms:
AADB2C90232: The provided id_token_hint parameter does not contain an accepted issuer. Please provide another token and try again. Correlation ID: 1f9cd754-7033-40ea-91a2-b2f91b867fb9 Timestamp: 2020-06-29 13:05:18Z
I saw that the issuer in the the token is related to localhost:
{
"alg": "RS256",
"kid": "1D8082E33223E5EA5094B62B4BB5B3944779D3AD",
"x5t": "HYCC4zIj5epQlLYrS7WzlEd5060",
"typ": "JWT"
}.{
"name": "Western Miller",
"email": "test#test.be",
"nbf": 1593436327,
"exp": 1594041127,
"iss": "https://localhost:44351/",
"aud": "ba6d05ab-ec87-4d04-b83f-dc62ebb727d8"
}.[Signature]
Anyone who knows what I need to place in the iss that's working by just running locally?
This occurs when the id_token_hint you generate in your web service contains an issuer claim (iss), that is not accepted by the id token hint technical profile.
See in the below XML, that the issuer item must contain a string that matches exactly your iss claim in the generated id_token_hint.
<TechnicalProfiles>
<TechnicalProfile Id="IdTokenHint_ExtractClaims">
<DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
<Protocol Name="None" />
<Metadata>
<!--Sample action required: replace with your endpoint location -->
<Item Key="METADATA">https://your-app.azurewebsites.net/.well-known/openid-configuration</Item>
<Item Key="IdTokenAudience">your_optional_audience_override</Item>
<Item Key="issuer">your_optional_token_issuer_override</Item>
</Metadata>
You should put https://localhost:44351/ in the issuer metadata item.
Although, I've set all the claim mappings well so they match those issued by our Identity Server 3, we don't seem to have those values on Azure AD side. Name and email are claims which can be used as an example. And which is weird, this happens only with Custom Identity Provider (Open ID Connect) while for example Facebook built-in Identity Provider works well and takes those claims received from IdP. Is there anyone who made this work ever?
[EDITED]
Additionally, I have also tried to achieve this trough custom polices as it was suggested here: How to store claims from IdentityServer 3 in Azure AD B2C or just include it in tokens issued by AAD B2C. Now, I'm facing with another problem to simply connect AAD B2C to Identity Server 3 by using custom policies. Here is my TechnicalProfile definition from TrustFrameworkExnsion.xml:
<TechnicalProfile Id="IdentityServerProfile">
<DisplayName>IdentityServer</DisplayName>
<Description>Login with your IdentityServer account</Description>
<Protocol Name="OpenIdConnect"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="METADATA">https://{identity_server_hostname}/identity/.well-known/openid-configuration</Item>
<Item Key="ProviderName">https://{identity_server_hostname}/identity</Item>
<Item Key="client_id">00000000-0000-0000-0000-000000000000</Item>
<Item Key="IdTokenAudience">00000000-0000-0000-0000-000000000000</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid profile customScope</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="AccessTokenResponseFormat">json</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_IdentityServerAppSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="IdentityServer" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="tid" />
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="sub" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
Basically, after authentication on IdentityServer side, I got redirected back to my web page which initialized the sign-in and then I get this error:
AADB2C: An exception has occurred.
Correlation ID: 6797f691-4adb-4963-ad12-f31add3e1919
Timestamp: 2018-08-23 08:42:54Z
While analyzing the log on AAD B2C for the given correlation ID, I didn't find anything useful which would lead me to the possible solution.
Yesterday, after quiet a long time spent on trying so many different things, I finally realized why we were not getting all claims back on the client. They actually didn't exist in identity token but only in access token. AAD B2C uses the first one, the identity token, while doing mappings defined in custom policies and that was the whole point. In the end I had to make some small changes on IdentityServer3 side (take a look at the code below).
This is how the class which is responsible for issuing claims and generating both identity and access tokens now looks like:
public class CustomClaimsProvider : DefaultClaimsProvider
{
private readonly IIndex claimDefinitions;
public CustomClaimsProvider(
IUserService users,
IIndex<string, IClaimsDefinition> claimDefinitions)
: base(users)
{
this.claimDefinitions = claimDefinitions;
}
public override async Task<IEnumerable<Claim>> GetIdentityTokenClaimsAsync(
ClaimsPrincipal subject,
Client client,
IEnumerable<Scope> scopes,
bool includeAllIdentityClaims,
ValidatedRequest request)
{
var claims = await base.GetIdentityTokenClaimsAsync(subject, client, scopes, includeAllIdentityClaims, request).ConfigureAwait(false);
return GetAdditionalClaims(scopes, claims);
}
public override async Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(
ClaimsPrincipal subject,
Client client,
IEnumerable<Scope> scopes,
ValidatedRequest request)
{
var claims = await base.GetAccessTokenClaimsAsync(subject, client, scopes, request).ConfigureAwait(false);
return GetAdditionalClaims(scopes, claims);
}
private IEnumerable<Claim> GetAdditionalClaims(IEnumerable<Scope> scopes, IEnumerable<Claim> claims)
{
var scopesList = scopes.ToList();
var claimsList = claims.ToList();
foreach (var scope in scopesList.Select(x => x.Name))
{
if (claimDefinitions.TryGetValue(scope, out IClaimsDefinition claimDef))
{
claimsList.AddRange(claimDef.GetClaims(claims));
}
}
return claimsList;
}
}
So, the main point is, you should also override GetIdentityTokenClaimsAsync method in the class derived from DefaultClaimsProvider if you want to have some additional claims as a part of your identity token.
Big thanks to Microsoft Support which helped me a lot with troubleshooting the issue.