Hosting the id_token_hint well-known endpoint in Azure AD B2C itself

public key by Juicy Fish from the Noun Project

There’s a good article on id_token_hint on docs.microsoft here.

I did a post on this here.

Basically, you pass information e.g. a username into B2C inside a signed JWT. The JWT is signed by a certificate.

B2C checks the JWT signature by accessing an Azure app service that contains a “/.well-known/openid-configuration” endpoint and B2C then extracts the public key from the “jwks_uri” endpoint contained inside it.

It uses the public key to verify the signature.

However, to upload a certificate i.e. a .pfx file to an app service, you have to scale-up the app service to at least a B level VM which is not free.

Looking through this B2C CIAM sample, I saw this:

Using B2C to generate the metadata endpoints

You can have B2C generate the below mentioned metadata endpoints if you don't wish to host these yourself.

  • /.well-known/openid-configuration, set this URL in the IdTokenHint_ExtractClaims technical profile
  • /.well-known/keys”

This looked promising as you don’t have to pay to upload certificates to B2C so that the custom policies can use them.

Essentially, there is a website within B2C that simply provides the well-known endpoints.

Note the instructions in the sample link above are incorrect. You cannot upload a .cer file — it has to be a .pfx file.

Following the instructions:

  1. In the “Policy Keys” blade, Click “Add” to create a new key and select “Upload” in the options.

2. Give it a name, something like “B2C_1A_AABIDToken” and browse to the .pfx file. Enter the password.

3. Create a dummy set of new base, extension and relying party files. You can do so by downloading it from the starter pack here. I used the pack for local accounts:

Image showing the base, extension and siginin custom policies

You only need these three.

I called the SUSI policy “B2C_1A_SIGNUP_SIGNIN_MLB2C”

4. If you have not setup custom policies from a starter pack before then follow the instructions here.

5. Once you have successfully setup the new starter pack policies, open the base file of this set and update the TechnicalProfile Id=”JwtIssuer”. Here we will update the token signing key container to the key we created in step 2.

6. Update B2C_1A B2C_1A_TokenSigningKeyContainer to B2C_1A_AABIDToken.

<CryptographicKeys>
<!-- For magic link -->
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_AABIDToken" />
...
</CryptographicKeys>

7. Upload this base file along with the extension and relying party if you haven’t done so yet.

8. Click on the relying party file in the B2C portal and copy the “OpenID Connect discovery endpoint”. This is the metadata you needed!

Image showing the OpenID Connect discovery endpoint of the RP i.e. https://tenant.b2clogin.com/tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_SIGNIN_MLB2C

The example I used to verify the signature is in this gist.

Note that this custom policy inherits from the extension file I normally use, not from the dummy set.

The dummy set is not used for anything other than hosting the well-known endpoint.

The code to generate the magic link is in the posts above.

Running this provides:

https://tenant.b2clogin.com/tenant.onmicrosoft.com/oauth2/v2.0/authorize?
p=B2C_1A_Username_ProofUp_MLB2C
&client_id=7bd...760
&redirect_uri=https://jwt.ms
&nonce=5f34e8e26c08401aaf0c04e184df25f2
&scope=openid
&response_type=id_token
&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6InVXeEpId0x1VE9hSFBwTFVRV2czY0NfLWZPdyIsIng1dCI6InVXeEpId0x1VE9hSFBwTFVRV2czY0NfLWZPdyIsInR5c...
dVj9NFjRyTvrLmjzEauR2b5G8O-u_j-s_fEiN5dZZYm_JSDTM834F5mSHz7LRJrRAZvISozwltU4YBy99ktSPhxGMveXD2eds1bjwDQR9MHc29EYQTCDh5da-q-7yfkjLEV-uNWNiRLtstIQMm20dzopV_5EIn7scdelKBSNnCXP_Y-wa4mw

The token in the magic link looks like:

{
"signInNames.userName": "magiclink",
"nbf": 1632095704,
"exp": 1632696904,
"iss": "http://localhost:57408/",
"aud": "7bd...760"
}

The default user journey in the gist is:

<DefaultUserJourney ReferenceId="ProofUp_MagicLink_B2C"/>

And this looks like:

<UserJourney Id="ProofUp_MagicLink_B2C">
<OrchestrationSteps>

<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_Asymmetric_ExtractClaims"/>

<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="Read-WithUsername" TechnicalProfileReferenceId="AAD-UserReadUsingUserName"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/></OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>

All it does is verify the JWT signature, extract the username out of the JWT, read B2C to get this user’s attributes and then return a JWT.

The first step calls “IdTokenHint_Asymmetric_ExtractClaims”.

<TechnicalProfile Id="IdTokenHint_Asymmetric_ExtractClaims">
<DisplayName>My ID Token Hint Asymmetric Technical Profile</DisplayName>
<Protocol Name="None"/>
<Metadata>
<!-- Replace with your endpoint location -->
<Item Key="METADATA">https://tenant.b2clogin.com/tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_SIGNIN_MLB2C</Item>
<Item Key="IdTokenAudience">7bd30dbe...1d14760</Item>
<Item Key="issuer">http://localhost:57408/</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.userName"/>
</OutputClaims>
</TechnicalProfile>

You can see that it looks for the “signInNames.userName” claim in the JWT in the magic link.

Note that the metadata points to the dummy SUSI policy “B2C_1A_SIGNUP_SIGNIN_MLB2C” in the B2C tenant, not to an app service.

Then it returns the JWT as expected.

My user has a username of “magiclink”.

"exp": 1632098293,
"nbf": 1632094693,
"ver": "1.0",
"iss": "https://tenant.b2clogin.com/65f...316/v2.0/",
"sub": "1ab...085",
"aud": "7bd...760",
"acr": "b2c_1a_username_proofup_mlb2c",
"nonce": "158...d3a",
"iat": 1632094693,
"auth_time": 1632094693,
"signInName": "501...0b8@tenant.onmicrosoft.com",
"name": "Magic Link",
"given_name": "Magic",
"family_name": "Link",
"idp": "Local",

All good!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Rory Braybrook

Rory Braybrook

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5