Azure AD B2C Custom Policy – Custom Claim (Token Enrichment)

Introduction:

Azure Active Directory B2C (Azure AD B2C) allows identity developers to seamlessly incorporate RESTful API interactions into their user flows using API connectors. This feature empowers developers to dynamically fetch data from external identity sources. By the end of this step-by-step guide, you’ll have the capability to establish an Azure AD B2C user flow that seamlessly interacts with APIs, enhancing tokens with information sourced from external repositories.


In this scenario, we enhance the user’s token data by seamlessly integrating with a corporate line-of-business workflow. When a user signs up or signs in with a local or federated account, Azure AD B2C triggers a REST API to retrieve the user’s extended profile data from a remote data source. In this example, Azure AD B2C transmits the user’s email address. The REST API then responds with the user’s age, represented as a random number.

AD B2C Token Enrichment
  Token Enrichment

Prerequisites:

Complete the steps in Get started with custom policies.

You should possess a functional custom policy for local account sign-up and sign-in.

Technical Profile with REST API endpoint:

In this guide, it is assumed that you possess a REST API capable of verifying whether a user’s Azure AD B2C email address is enlisted in your backend system. If the user is registered, the REST API provides the age of the user. The JSON code below exemplifies the data transmitted by Azure AD B2C to your REST API endpoint.

{
    "objectId": "User email address",
}

Once your REST API validates the data, it should return an HTTP 200 (Ok), with the following response:

{
    "age": "28"
}

Define Claims

A claim serves as a transient repository for data throughout the execution of an Azure AD B2C policy. Claims are defined within the claim’s schema section.

To incorporate claims into your policy, follow these steps:

  • Open the extensions file of your policy, such as TrustFrameworkExtensions.xml.
  • Locate BuildingBlocks element. If it’s not present, include it.
  • Locate the ClaimsSchema element. If it’s not present, include it.
  • Integrate the specified claims into the ClaimsSchema element.

<ClaimType Id="age">
  <DisplayName>Your Age</DisplayName>
  <DataType>29</DataType>
</ClaimType>

Define technical profile

A Restful technical profile facilitates communication with your custom RESTful service. Azure AD B2C transmits data to the RESTful service through an InputClaims collection and receives data in return through an OutputClaims collection. To implement this, locate the ClaimsProviders element in your TrustFrameworkExtensions.xml file and incorporate a new claims provider using the following specifications:

<ClaimsProvider>
  <DisplayName>REST APIs</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="REST-GetProfileAge">
      <DisplayName>Get user extended profile Azure Function web hook</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <Metadata>
        <!-- Set the ServiceUrl with your own REST API endpoint -->
        <Item Key="ServiceUrl">https://your-account.azurewebsites.net/api/GetProfile?code=your-code</Item>
        <Item Key="SendClaimsIn">Body</Item>
        <!-- Set AuthenticationType to Basic or ClientCertificate in production environments -->
        <Item Key="AuthenticationType">None</Item>
        <!-- REMOVE the following line in production environments -->
        <Item Key="AllowInsecureAuthInProduction">true</Item>
      </Metadata>
      <InputClaims>
        <!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="email" />      </InputClaims>
      <OutputClaims>
        <!-- Claims parsed from your REST API -->
        <OutputClaim ClaimTypeReferenceId="age" />
      </OutputClaims>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

From the above code, the email will be sent to the REST service within the JSON payload. And the response age will assign to the claim age.

  1. ServiceUrl: Define the URL for the REST API endpoint.
  2. SendClaimsIn: Specify the method for sending input claims to the RESTful claims provider.
  3. AuthenticationType: Choose the authentication type employed by the RESTful claims provider, such as Basic or ClientCertificate.
  4. AllowInsecureAuthInProduction: Ensure to set this metadata to false in a production environment to enhance security.

Azure Function

I just created a sample REST API with azure function. The below function will give the age, if there is an email address from the input request. Basically, we can extend this functionality to connect with other external source to get the data based on certain validation, but I’m just leaving it with a static data for a demo purpose.

  public static class GetUserAge
    {
        [FunctionName("GetUserAge")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
          
            if (req.Body == null) throw new Exception();
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            if (string.IsNullOrEmpty(requestBody))
            {
                return (ActionResult)new ConflictObjectResult(
                     new B2CResponseContent("Request content is empty", HttpStatusCode.Conflict));
            }
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            string email = data?.email;
            if (!String.IsNullOrEmpty(email))
            {
                return (ActionResult)new OkObjectResult(new OutputClaimsModel()
                {
                    age = "27",
                    
                });
            }
            else
            {
                return (ActionResult)new ConflictObjectResult(
                    new B2CResponseContent("Something went wrong", HttpStatusCode.Conflict));
            }
        }
    }

Update the orchestration step

A user journey is depicted as a sequence of orchestrated steps that must be adhered to for a transaction to succeed. It is possible to customize the orchestration by adding or removing steps. In this instance, the intention is to introduce a new orchestration step designed to enhance the information delivered to the application following a user’s sign-up or sign-in, accomplished through a REST API call.

  • Open the foundational file of your policy, such as TrustFrameworkBase.xml.
  • Locate the <UserJourneys> element, copy the entire element, and subsequently remove it.
  • Open the extensions file of your policy, for example, TrustFrameworkExtensions.xml.
  • Paste the <UserJourneys> into the extensions file after the closure of the <ClaimsProviders> element.
  • Identify the <UserJourney Id=”SignUpOrSignIn”> and include the specified orchestration step before the final one.
<OrchestrationStep Order="4" Type="ClaimsExchange">
  <ClaimsExchanges>
    <ClaimsExchange Id="RESTGetProfile" TechnicalProfileReferenceId="REST-GetProfileAge" />
  </ClaimsExchanges>
</OrchestrationStep>
 <OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" /> 

Apply last two steps for the ProfileEdit and PasswordReset user journeys.

Attach a claim with the token

When you add an output claim, it becomes part of the token after a successful user journey and is then sent to the application. To include the “age” as an output claim, modify the technical profile element within the relying party section.

Open SignUpOrSignIn.xml file and add the new output claim ‘age’

<RelyingParty>
  <DefaultUserJourney ReferenceId="SignUpOrSignIn" />
  <TechnicalProfile Id="PolicyProfile">
    <DisplayName>PolicyProfile</DisplayName>
    <Protocol Name="OpenIdConnect" />
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="displayName" />
      <OutputClaim ClaimTypeReferenceId="givenName" />
      <OutputClaim ClaimTypeReferenceId="surname" />
      <OutputClaim ClaimTypeReferenceId="email" />
      <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
      <OutputClaim ClaimTypeReferenceId="identityProvider" />
      <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
      <OutputClaim ClaimTypeReferenceId="age" DefaultValue="" />
    </OutputClaims>
    <SubjectNamingInfo ClaimType="sub" />
  </TechnicalProfile>
</RelyingParty>

Follow the same procedure for the ProfileEdit.xml and PasswordReset.xml user journeys. Save the modifications made to the following files: TrustFrameworkBase.xml, TrustFrameworkExtensions.xml, SignUpOrSignin.xml, ProfileEdit.xml, and PasswordReset.xml.

Test the custom policy

Select your relying party policy, for example B2C_1A_signup_signin.

For Application, select a web application that you previously registered. The Reply URL should show https://jwt.ms.

Select the Run now button.

Run the user flow

From the sign-up or sign-in page, select Sign up now to sign up. Finish entering the user information including the city name, and then select Create. You should see the contents of the token that was returned.

User flow response

From the above image it is obvious that the custom claim age has been embedded with the token and decoded.

Summary:

We have seen how to create a technical profile to do the REST API call and updating the sign in/up orchestration step to customize the token by adding a custom claim.

Get the extension file from GitHub

gowthamk91

Leave a Reply

Discover more from Gowtham K

Subscribe now to keep reading and get access to the full archive.

Continue reading