Invoking the Azure AD B2C custom policy journey with a JWT
The use case for this was a registration flow outside of B2C that ended with a reset password request.
The user is created via the Graph API with a dummy password. At no stage is the user aware of this password. The user then needs to reset their own password and ideally a JWT would be sent to the application.
The registration flow could invoke a custom password reset web page and this web page could then call the Graph API to reset the password.
This page would need some kind of security to avoid someone discovering the URL and using it to change any password.
There is a current B2C password reset page but this page requires the user to first validate their email address. It would not be a good user experience for the user to go through the flow and right at the end be asked to do the validation.
So I wondered if we could use the current B2C password reset page but remove the validation step and pass in the email address of the user in some secure manner. Obviously, once you remove the validation step the page is wide open to be hacked.
Some investigation revealed that this could be done by putting the email into a JWT and sending that to B2C. A MIM attack wouldn’t work because if they tried to change the email address that would invalidate the signature.
So we need a custom password reset policy.
There is an example of all the code required in this sample.
The first step is to generate the JWT. This is “Program.cs” in a console application.
We provide the issuer, the audience (the redirect URI) and a secret key to be used in the signing process. The result is a signed JWT that is displayed in the cmd prompt.
Now we move to the custom policy.
We need a RelyingParty.
Notice that InputTokenFormat is “JWT ” and the key is in a B2C key container.
The JWT generation adds a claim called “email” and this is matched to the
<InputClaim ClaimTypeReferenceId=”email” />
The secret key has to be shared between the code that generates the token and B2C. This means that it has to be a manual key since there is no way to get the value of a generated key after it has been created.
Now we need a “User Journey”. This is called by the RelyingParty.
We can use the existing “Password Reset” template as a start to get:
This is added to the TrustFrameworkExtensions file.
We want to:
- Display a password reset page
- Take the user email address and convert to a user objectID
- Use the objectID to write the new password to B2C
This a accomplished by:
both of which are in the user journey.
The Technical Profile for “LocalAccountWritePasswordUsingObjectId” contains:
and has the following output claims:
<OutputClaim ClaimTypeReferenceId=”newPassword” Required=”true” />
<OutputClaim ClaimTypeReferenceId=”reenterPassword” Required=”true” />
The ContentDefinition for “api.localaccountpasswordreset” contains:
SelfAsserted implies a user input form that contains the two password fields as per the output claims.
To kick this all off, the URL is of the form:
&client_assertion=generated JWT from console application
Running the URL shows:
Note that B2C will validate the JWT including expiry and signature. The token is valid for twenty minutes. Invoking the URL after say 30 minutes results in:
Using the User Journey shows:
This flow could e.g. be invoked from a SPA as:
Note that this code will result in the user being signed in — exactly the same as a normal B2C “Forgotten password” flow.