Token Enrichment with Azure B2C

Recently while working with a client, we were asked to implement an Authorization model that could crosscut our various services with the identity tying back to B2C and the OAuth pattern in use. A lot of ideas were presented, and it was decided to explore (and ultimately use) the preview feature Token Enrichment: https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-api-connector-token-enrichment?pivots=b2c-user-flow

How does OAuth handle Authorization?

Prior to the rise of OAuth, or delegated authentication really, authorization systems usually involved database calls based on a user Id which was passed as part of a request. Teams might even leverage caching to speed things up but, in the world of monoliths this was heavily used, realistically for most teams there was no alternative.

Fast forward to the rise in distributed programming and infrastructure patterns like Microservices or general approaches using SOA (Service Oriented Architecture) and this pattern falls on its face, and hard. Today, the very idea of supporting a network call for EVERY SINGLE request like this is outlandish and desperately avoided.

Instead, teams leverage an identity server concept (B2C here, or Okta, Auth0 [owned by Okta] or Ping] whereby a central authority issues the token and embeds the role information into the token’s contents; the names of roles should never constitute sensitive information. Here is a visual:

The critical element to understand here is that tokens are hashed and signed. Any mutation of the token will render it invalid and unable to pass any authoritative check. Thus, we just need to ensure no sensitive data is exposed in the token, as they can be easily decoded by sites like https://jwt.ms and https://jwt.io

Once the Access Token is received by the service and it is verified, the service can then strip claims off the token and use it for its own processing. I wont be showing you in this article BUT dotnet (and many other web frameworks) natively support constructs to make this parsing easy and enable easy implementation of Auth systems driven by claims.

How do I do this with B2C?

B2C supports API Connectors per the article above. These connectors allow B2C to reach out at various stages and contact an API to perform additional work; including enrichment.

The first step in this process is the creation of a custom attribute to be sent with the Access Token to hold the custom information, I called mine Roles.

Create the Custom Attribute for ALL Users

  1. From your Azure B2C Tenant select User Attributes
  2. Create a new Attribute called extension_Roles of type string
  3. Click Save

The naming of the attribute here is crucial. It MUST be preceded be extension_ for B2C to return the value.

This attribute is created ONLY to hold the value coming from token enrichment via the API, it is not stored in B2C, only returned as part of the token.

Configure your sign-in flow to send back our custom attribute

  1. Select User Flows from the main menu in Azure B2C
  2. Select your sign-in flow
  3. Select Application claims
  4. Find the custom claim extension_Roles in the list
  5. Click Save

This is a verification step. We want to ensure our new attribute is the Application Claims for the flow and NOT, the user attributes. If it is in the user attributes, it will appear in the sign-up pages.

Deploy your API to support the API Connector

The link at the top shows what the payload to the API connector looks like as well as the response. I created a very simple response in an Azure Function, shown below:

public class HttpReturnUserRolesFunction
{
[FunctionName("HttpReturnUserRoles")]
public IActionResult HttpReturnUserRoles(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
return new OkObjectResult(new {
version = "1.0.0",
action = "Continue",
postalCode = "12349",
extension_Roles = "Admin,User"
});
}
}
view raw function.cs hosted with ❤ by GitHub

We can deploy this the same way we deploy anything in Azure; in my testing I used right-click publishing to make this work.

Setting the API Connector

We need to configure B2C to call this new endpoint to enrich the provided token.

  1. Select API Connectors from the B2C menu
  2. Click New API Connector
  3. Choose any Display name (I used Enrich Token)
  4. For the Endpoint URL input the web page to your API
  5. Enter whatever you want for the Username and Password
  6. Click Save

The username and password can provide an additional layer of security by sending a base64 encoded string to the API endpoint which the endpoint can decode and validate the caller is legitimate. In the code above, I choose not to do this, though I would recommend it for a real scenario.

Configure the Signup Flow to call the Enrich Token API endpoint

The last step of the setup is to tell the User Flow for Signup/Sign-in to call our Enrich Token endpoint.

  1. Select User Flows
  2. Select the User Flow that represents the Signup/Signin operation
  3. Select API Connectors
  4. For the Before including application claims in token (preview) select the Enrich Token API Connector (or whatever name you used)
  5. Click Save

This completes the configuration for calling the API Connector as part of the user flow.

Testing the Flow

Now let’s test our flow. We can do this using the built-in flow tester in B2C. Before that though, we need to create an Application Registration and set the a reply URL so the flow has some place to dump the user when validation is successful.

For this testing, I recommend using either jwt.ms or jwt.io which will receive the token from B2C and show its contents. For more information on creating an Application Registration see this URL: https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga

Once you have created the registration return to the B2C page and select User Flows. Select your flow, and then click Run User Flow. B2C will ask under what identity do you want to run the flow as. Make sure you select the identity you created and validate that the Reply URL is what you expect.

Click Run user flow and login (or create a user) and you should get dumped to the reply URL and see your custom claims. Here is a sample of what I get (using the code above).

{
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk",
"typ": "JWT"
}.{
"ver": "1.0",
"iss": "b2clogin_url/v2.0/",
"sub": "d0d196a4-96b3-4c46-b550-842ab59cd4d8",
"aud": "3a61cc01-104a-44c8-a3ff-d895a860d70e",
"exp": 1695000577,
"nonce": "defaultNonce",
"iat": 1694996977,
"auth_time": 1694996977,
"extension_Roles": "Admin,User",
"tfp": "B2C_1_Signup_Signin",
"nbf": 1694996977
}.[Signature]
view raw token.json hosted with ❤ by GitHub

Above you can see extension_Roles with the value Admin,User. This token can then be parsed in your services to check what roles are given to the user represented by this token.

Leave a comment