Azure AD B2C Custom Policy Calling a REST API to get Additional Claims

Published by Mika Berglund on

In this article I’ll show you how you can implement an Azure AD B2C custom policy that calls a REST API. This API returns additional claims that Azure AD B2C includes in the tokens it issues. From such an API, you can then connect to whatever data source you need to get the claims you want to use to describe a user logging in to your application.

Naturally, you can use custom policies that you build like this as any other policy in Azure AD B2C. I’ve written about ways to leverage Azure AD B2C in several previous articles covering Azure AD B2C.

Technically, you could add your own claims just by adding custom user attributes and including those in your tokens. However, there are several limitations to this. For instance, there is no user interface to edit those attributes with. With custom policies you have more freedom with how and where claims are managed.

I’ve created a repository on GitHub containing the policies and sample code related to this article. The policies in the repo are based on the excellent Azure AD B2C Custom Policy Started Pack from the Azure AD B2C team. In my sample code, I’ve used only the sample policies for local accounts, to keep things as simple as possible. You can easily apply the same principle to any of the other kinds of policies in the starter pack.

Support for Different Environments

Usually you don’t have just one environment that you run your applications in. You probably have at least a test environment, in addition to the production environment that your users use. Typically, you should try to separate these environments as much as possible. From an Azure AD B2C perspective, this also means two separate tenants. With two separate tenants you get benefits like different user accounts and different environments.

Policies Are Unique for Every Tenant

Azure AD B2C policies are unique for each tenant that you deploy them to. When connecting to a REST API, you probably also have a different endpoint you connect to in different environments.

This means that you have to modify your policies slightly for each environment. That’s why I’ve added the concept of several environments to the policies in my sample code. I’ve done that by replacing the tenant-specific information in the policy files with placeholders. I also added a command-line application that you then use to process the policy files and replace the placeholders with values for each of your environment. The policy files contain the following place holders.

  • {{yourtenant}} – The name of your Azure AD B2C tenant, i.e. {{yourtenant}}.onmicrosoft.com
  • {{IdentityExperienceFrameworkAppId}} – The application ID (client ID) of your Identity Experience Framework App (see below for details).
  • {{ProxyIdentityExperienceFrameworkAppId}} – The application ID (client ID) of your Proxy Identity Experience Framework App (see below for details).
  • {{TokenSigningKeyContainerName}} – The name of the token signing key container (see below for details).
  • {{TokenEncryptionKeyContainerName}} – The name of the token encryption key container (see below for details).
  • {{ClaimsApiUrl}} – The full URL to the API endpoint that the policy will call to get claims for a given user. This sample assumes that the API uses an API key for authentication that can be specified in the URL, as is the case with Azure Functions, for instance.

Note! If you want/need to use a different authentication mechanism, then you need to add your own additional placeholders with the required data. Azure AD B2C custom policies support several authentication mechanisms.

BuildCustomPolicy Command-Line Utility

To produce the policy files for an environment, you run the BuildCustomPolicy command-line application, and specify the environment-specific values as command-line arguments, like this:

BuildCustomPolicy[.exe]
    -e|--environment
    -t|--tenant
    -a|--appid
    -p|--proxyappid
    -s|--signingkeycontainer
    -c|--encryptionkeycontainer
    -u|--claimsapiurl

These arguments are:

  • environment – The name of your environment. This can be anything you find useful. The command-line utility stores the produced policy files in a sub folder with the name of the environment. This will keep your policy files separate for each environment.
  • tenant – The value of the {{yourtenant}} placeholder.
  • appid – Specifies the value for the {{IdentityExperienceFrameworkAppId}} placeholder.
  • proxyappid – The value for the {{ProxyIdentityExperienceFrameworkAppId}} placeholder.
  • signingkeycontainer – This is the value for the {{TokenSigningKeyContainerName}} placeholder.
  • encryptionkeycontainer – The value for the {{TokenEncryptionKeyContainerName}} placeholder.
  • claimsapiurl – Contains the value for the {{ClaimsApiUrl}} placeholder.

Preparing Your Azure AD B2C Tenant

Now that we have the grand picture clear, it’s time to start working on your environment. Here, environment mainly refers to the Azure AD B2C tenant that you are using. You find a description of all the necessary steps in this detailed documentation, so I will just highlight the most important things here to make it more readable, like a checklist.

Create a Signing Key

Azure AD B2C needs this key to digitally sign the tokens. Name the key TokenSigningKeyContainer, set its type to RSA and usage to Signature.

This name is the value for your {{TokenSigningKeyContainerName}} placeholder and --signingkeycontainer command-line argument.

Read more…

Create an Encryption Key

Azure AD B2C uses this key to encrypt data. Name the key TokenEncryptionKeyContainer, set its type to RSA and usage to Encryption.

This name is the value for your {{TokenEncryptionKeyContainerName}} placeholder and --encryptionkeycontainer command-line argument.

Read more…

Register the Identity Experience Framework Application

Azure AD B2C uses this application to interact with your users signing up, signing in, and performing the actions that you enable in your policies.

  • Select new registration and give the application a name, for instance IdentityExperienceFramework.
  • Allow only accounts in the same directory to use the application.
  • As the redirect URI for the application, select Web and set the URI to https://{{yourtenant}}.b2clogin.com/{{yourtenant}}.onmicrosoft.com, where {{yourtenant}} is the same as described above.
  • Make sure to check the Grant admin consent to openid and offline_access permissions checkbox.

Create the application. Next you will need to create a scope in the application.

  • Go to Expose an API for the application you just created.
  • Add the scope user_impersonation
    • Admin consent display name: Access IdentityExperienceFramework
    • Admin consent description: Allow the application to access IdentityExperienceFramework on behalf of the signed-in user.

The application (client) ID for this application is the value for your {{IdentityExperienceFrameworkAppId}} placeholder and --appid command-line argument.

Read more…

Register the Proxy Identity Experience Framework Application

The proxy app is a native application with delegated permissions to the web application you created above.

  • Select new registration, and give the application a name, for instance ProxyIdentityExperienceFramework
  • Allow only accounts in the same directory to use the application.
  • Set the redirect URI to Public client, and set the URI to myapp://auth
  • Make sure to check the Grant admin consent to openid and offline_access permissions checkbox.
  • Create the application.
  • Make sure app mobile and desktop flows are set to Yes in the Authentication section under Advanced settings.
    • This was previously found in the portal UI as Treat application as a public client.

Then you need to grant the user_impersonation permission to the web application you created above. Make sure that you grant admin consent to the permissions so that users don’t have to do that separately.

The application (client) ID for this application is the value for the {{ProxyIdentityExperienceFrameworkAppId}} placeholder and --proxyappid command-line argument.

Read more…

Policy File Structure for Azure AD B2C Custom Policies

Before we dive into modifying the policy files, let’s have a look at the policy file structure.

Policy files build up a hierarchy. This hierarchy resembles that of classes in C#, where one class can define the base implementation it extends. Policy files also specify base policies that they extend by adding more definitions. The policy inheritance structure in the accompanying sample code is:

  • TrustFrameworkBase.xml – You find the majority of the policy definitions in this policy file. Typically does not require modifications when creating custom policies.
    • TrustFrameworkExtensions.xml – Inherits from the base policy file. Contains all of your modifications that you want to have available to all policies that you define.
      • SignUpOrSignin.xml – The policy used to sign your users in, or allow them to sign up for your application.
      • ProfileEdit.xml – The policy that allows your users to edit their profiles. Since you are building a policy that accesses claims from an external system, it might not make sense to have this policy, but rather provide your users with custom functionality for modifying their profiles, that you then store in your external system.
      • PasswordReset.xml – The policy used by your users to reset their passwords.

Defining Your Custom Policy Claims

Now that you have properly configured your tenant, and we’ve had a look at the policy file structure, it’s time to start looking at the policies. The first thing we’ll do is defining the claims we use in the policies. You have to define each claim in the policies. For instance, including any claim that your REST API returns in the issued token is not a feature that Azure AD B2C custom policies support.

You define your claims in the TrustFrameworkExtensions.xml policy file by editing the contents of the ClaimsSchema element. The element is a child element of the TrustFrameworkPolicy/BuildingBlocks element. Inside the ClaimsSchema element, you add one ClaimType element for each claim you want to define in your policies. In my sample, I have added two claims: role and upn. The role claim type is defined as a collection of strings, meaning that a role claim can have multiple values. The roles are then associated with the user specified by the upn claim, which is defined as a string claim. This means that the upn claim can only have one value. See the full list of data types supported by Azure AD B2C.

The code snippet below shows the complete ClaimsSchema element.

<ClaimsSchema>
  <ClaimType Id="role">
    <DisplayName>Role</DisplayName>
    <DataType>stringCollection</DataType>
    <UserHelpText>Contains one or more roles associated with a user.</UserHelpText>
  </ClaimType>
  <ClaimType Id="upn">
    <DisplayName>User Principal Name</DisplayName>
    <DataType>string</DataType>
    <UserHelpText>The User Principal Name uniquely identifies the user.</UserHelpText>
  </ClaimType>
</ClaimsSchema>

Defining the API Endpoint to Connect to From Azure AD B2C Custom Policy

You define the REST API that the policy calls to get additional claims from as a claims provider. Again, we’ll add that to the TrustFrameworkExtensions.xml policy file. A claims provider is specified using the ClaimsProvider element. Most of the definition of a claims provider goes into the TechnicalProfiles child element.

To call your custom API from an Azure AD B2C custom policy, you have to set the Protocol to use the built-in RESTful provider. Then you just specify the URL of the endpoint, and how to authenticate with the API. Azure AD B2C supports several authentication mechanisms including None, Basic, Bearer or Client Certificate. In the sample we use None, and assume that the API endpoint supports API keys sent in the URL. You can read more about authenticating in your custom policies here.

Note that in the sample, we also set the AllowInsecureAuthInProduction metadata field to true, because sending the API key in the URL is not regarded as authentication by Azure AD B2C.

What we also specify in the technical profile, is what claims we want to send to the API endpoint, and what claims we assume it will return. These are defined in the InputClaims and OutputClaims elements. Note, that for the signInName input claim we set the PartnerClaimType to upn. This means that the signInName claim will be sent to the API endpoint as upn instead. Mapping input and output claims like this gives you more freedom to implement your API or use existing APIs without having to modify them too much.

The complete claims provider definition looks like this.

<ClaimsProvider>
  <DisplayName>External Claims Source</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="GetUserClaimsFromAPI">
      <DisplayName>Get claims for user from external system.</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <Metadata>
        <Item Key="ServiceUrl">{{ClaimsApiUrl}}</Item>
        <Item Key="SendClaimsIn">Body</Item>
        <Item Key="AuthenticationType">None</Item>
        <Item Key="AllowInsecureAuthInProduction">true</Item>
      </Metadata>
      <InputClaims>
        <!-- Claims sent to your REST API -->
        <InputClaim ClaimTypeReferenceId="objectId" />
        <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="upn" />
      </InputClaims>
      <OutputClaims>
        <!-- Claims parsed from your REST API -->
        <OutputClaim ClaimTypeReferenceId="role" />
        <OutputClaim ClaimTypeReferenceId="upn" />
      </OutputClaims>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

Calling the API During a User Journey

The last thing we need to do is to add a reference to our custom claims provider (the API endpoint) to one or more of the policies that we define. The policies define user flows, or user journeys, that specify what steps to take during sign-in for instance.

In the starter pack, the user journeys are all defined in the TrustFrameworkBase.xml policy file. However, since we have all our modifications in the TrustFrameworkExtensions.xml policy file, we need to move the user journeys.

Open the TrustFrameworkBase.xml file and locate the UserJourneys element. Copy and delete that element from the TrustFrameworkBase.xml file, and paste it into the TrustFrameworkExtensions.xml file, as the last element before the closing TrustFrameworkPolicy element.

For every policy, there is one user journey. Each journey that you want to include your custom claims in, you need to add the following orchestration step. Add this step as the second last in every journey, just before the SendClaims orhcestration step.

<OrchestrationStep Order="[order number]" Type="ClaimsExchange">
  <ClaimsExchanges>
    <ClaimsExchange Id="RESTGetUserClaims" TechnicalProfileReferenceId="GetUserClaimsFromAPI" />
  </ClaimsExchanges>
</OrchestrationStep>

Note that the Order attribute needs to be corrected to match the order in each policy, since the number of steps is different in each policy. Also make sure that you change the order for the SendClaims step that comes after the step you inserted.

Tips for Local Development for Azure AD B2C Custom Policies

It is very likely, that you don’t have an existing REST API to connect to. But instead, you are developing your own for your Azure AD B2C custom policy. And if you are building your own REST API, you are also very likely to develop that as an Azure Functions application.

Wouldn’t it be cool if you could wire up your policies running in Azure AD B2C to call into your API running locally in debug mode? Luckily there is a very easy way to do that with ngrok.

Ngrok is kind of a reverse proxy that exposes a public URL that is routed back to your application running locally on localhost. It is very easy to download and install.

After you have ngrok configured, you just run the following command in a shell window.

ngrok http 7071

This will create a random public host name under ngrok.io that you can use for both HTTP and HTTPS traffic from anywhere on the Internet. Port 7071 is the port that the Azure Functions runtime uses by default when running the application locally. Naturally, if you have changed that, you need to specify the correct port.

The output of the tool includes the following information.

Forwarding http://[random string].ngrok.io -> http://localhost:7071
Forwarding https://[random string].ngrok.io -> http://localhost:7071

You can use these URLs from anywhere, as long as the console application is running. The tunnel is closed when you press Ctrl+C. This means that if you use the URL http://localhost:7071/api/Claims locally to get claims for a user, you would map that in ngrok to https://[random string].ngrok.io/api/Claims. That is the URL that you need to specify in your custom policy using the --claimsapiurl command-line argument described for the BuildCustomPolicy command-line utility described above.

Summary

The sample policies in the source code associated with this article are simplified as much as possible in order to be able to focus on the actual subject. Still, I think the sample provides enough information for you to be able to adapt it to your needs.

Further Reading

The following links have been helpful for me when trying to figure out how to build custom policies for Azure AD B2C.


7 Comments

Ed · February 9, 2021 at 15:31

Very clear, this helped me a lot. Thank you!

cs · March 12, 2021 at 17:41

Really great and detailed post!
I am unable to find a link or other description of how to create a Azure Function which should provide the additional claims to the token?

I guess i would need a db with the userobjectId and a “role” which the function would get every time a token is issued. How is this done within a function?

    Mika Berglund · April 7, 2021 at 09:32

    Hi!

    I’m sorry for a bit of a delay in my response. I will add a sample Azure Functions application to the sample code associated with this article, and update this article with references to that sample. I’ll let you know when it’s available.

      IW · December 5, 2021 at 02:01

      HI

      any update on when your function sample will be available?

      I have my custom policy configured as per above, but i get an error with:

      “The claims exchange specified in step 7 returned http error response that could not be parsed.”

      My claim is setup for a stringCollection and I am returning what I thought was a stringCollection from my API so i’m going wrong somewhere….

      StringCollection collection = new StringCollection();
      foreach (var element in Tenants)
      {
      collection.Add(element.TenantGuid.ToString());
      }

      Any guidance is much appreciated.

        Mika Berglund · February 3, 2022 at 08:14

        Hi!

        I’m very sorry for this delay. I still have not any sample code to share with you, but here are a few pointers that may get you in the right direction, in case you are still working on this.

        The function that Azure AD B2C will connect to is a normal HTTP triggered function. Naturally you need to have some kind of authentication implemented for that function. The easiest way would be to use a function key, i.e. the one that you either specify in a x-functions-key header, or add in the URL to the function using the code query string parameter. Then you just configure the URL with the code parameter in your custom policy.

        Azure AD B2C will send the claims of the user to your function in a POST request, where the payload is a JSON document. Each property (or attribute) in that document represent one claim.

        Your function must then return a similar JSON document with the claims that you want to add to the user – Each claim as a property in the JSON document.

        Hope this helps you a bit on the way.

Rich · May 18, 2023 at 18:33

This really helped! One thing I didn’t see in your post, you have to add the output claim to the .xml file you’re customizing. For example, I added AspNet roles to my token and I had to add the output claim to SignUpOrSignin.xml ()

    Mika Berglund · June 20, 2023 at 07:25

    Thanks for your feedback! I’ll see what I can do to make the article clearer and more informative. But you are right. You have to define all your custom claims that you want to return from your REST API.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *