“Hello World and Then Some” with Azure Active Directory B2C Custom Policies – Part 3: Hello [Your Name Here]!

This post continues a series that provides a walkthrough illustrating how to work with Azure Active Directory B2C custom policies by building one from the ground up. As the series progresses, the topics will serve to show how the different parts of the AAD B2C policy syntax and the underlying framework can be brought together to realize desired functionality. At the conclusion of the series, you should be ready to both understand existing AAD B2C policies and to create new ones for yourself.

The topic areas being covered in this series are:

Topic Focus Area
Introduction
Hello World! Returning a simple message with a custom policy
Hello [Your Name Here]! <– You are here Custom policy elements and syntax, how to collect and work with user input
Hello! (But Only If You Have an Access Code) How to perform input validation
Hello! (NOW WITH REST!) How to make REST calls to external services for validation or information exchange
Hello! Let’s Add a User How to use the backing Azure Active Directory database to store and retrieve user information
Hello! Please Either Sign In or Sign Up How to either sign in an existing user or allow a new user to sign up
Hello! It’s Time To Clean Up a Bit How to use multiple files and a policy file hierarchy to manage and reuse policy content


NOTE

This walkthrough is intended for developers who want to learn how to leverage AAD B2C Custom Policies. The content assumes you understand basic AAD B2C concepts such as how to make use of the built-in policies to implement basic user management and authentication tasks. Basic functional tutorials for AAD B2C can be found in the online documentation here.

Catching Up

In the previous posts in this series, you saw how to assemble and upload some basic AAD B2C policies. The first policy that you created could do little more than survive the parsing process during upload. The second policy was able to produce a JWT token that contained a claim with the words “Hello World” in it. You also learned how you could use the jwt.ms website together with the AAD B2C Run Now functionality to test your custom policies.

In this post we will add more functionality to our basic “Hello World” policy. Instead of replying with a static message that is hardcoded into the policy file, running the policy will instead show a user interface that will collect the user’s name, after which the message claim in the token that is produced by the policy will contain a more personal greeting. Along the way, we will do a much more thorough job of exploring and explaining the structure of AAD B2C policy files. This includes revisiting and explaining the content that is already in the “Hello World!” policy.

Let’s get started…

The TrustFrameworkPolicy Schema

It’s time to take a more thorough look at the contents of an AAD B2C policy. The following 5 key sections make up an AAD B2C policy. These get included as child elements within the root TrustFrameworkPolicy element:

  • BasePolicy This section contains elements that are used to build an inheritance hierarchy among policy files. We won’t present this section here – it will be discussed in detail in the Hello! It’s Time To Clean Up a Bit post a bit later in this series.
  • BuildingBlocks This section can be considered the “specification” section of the policy – it includes declarations for claims (which act as the “variables” in a policy), data transformation utilities, user interface content and controls, and localization configuration. The elements defined in here are later leveraged by the policy’s “functions” in the ClaimsProviders and RelyingParty sections.
  • ClaimsProviders This section defines items which can be considered the executable functions of the programming language, expressed as units called Technical Profiles.
  • UserJourneys This section defines the sequence of steps that are executed when the policy is invoked and the conditions under which each step is either invoked or skipped.
  • RelyingParty This section defines the behavior of the endpoint that is exposed by the policy. The primary purpose of this element is to define which UserJourney is executed and which claims are returned by the policy, although there are other settings that can also be defined.

These sections are included in an AAD B2C custom policy as child elements of the root TrustFrameworkPolicy element:

<TrustFrameworkPolicy
    xmlns_xsi="https://www.w3.org/2001/XMLSchema-instance"
    xmlns_xsd="https://www.w3.org/2001/XMLSchema"
    
    PolicySchemaVersion="0.3.0.0"
    TenantId="YOUR_TENANT_NAME.onmicrosoft.com"
    PolicyId="YOUR_POLICY_ID"
    PublicPolicyUri="http://YOUR_TENANT_NAME.onmicrosoft.com/YOUR_POLICY_ID">
    <BasePolicy>
        <!-- Content -->
    </BasePolicy>
    <BuildingBlocks>
        <!-- Content -->
    </BuildingBlocks>
    <ClaimsProviders>
        <!-- Content -->
    </ClaimsProviders>
    <UserJourneys>
        <!-- Content -->
    </UserJourneys>
    <RelyingParty>
        <!-- Content -->
    </RelyingParty>
</TrustFrameworkPolicy>

In-depth documentation for each of these elements is available online. Note that the documentation for the BasePolicy element is currently contained within the documentation page for the TrustFrameworkPolicy element.

Getting the XML Right(ish) – Using the TrustFrameworkPolicy Schema File

In order to validate the contents of AAD B2C policy files and – if your IDE supports it – get assistance when editing them, help is available in the form of a pre-built XML Schema file. You can find this file at the GitHub repository that houses the AAD B2C Starter Pack, which is mentioned in the “Get started with custom policies in Azure Active Directory B2C” article that was briefly discussed in the previous post in this series. Please recall that we are not using that Getting Started guide or this series, but instead taking a more granular bottom-up approach to learn how the details of the policy files work.

You can download the schema file directly from https://raw.githubusercontent.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/master/TrustFrameworkPolicy_0.3.0.0.xsd

The steps for using the schema to help validate your policy files depends on the IDE you are using. With Visual Studio, you can easily include schema references for XML documents. To do so, open the XML file for editing in Visual Studio, click the XML menu, and select Schemas… to open the XML Schemas dialog. From this dialog you can either select or add the schema files you want to use. Once the TrustFrameworkPolicy_0.3.0.0.xsd file is selected, click OK to dismiss the dialog. Visual Studio will now provide validation and IntelliSense for your XML policy. You can read more about using schema files with XML documents in Visual Studio in the XML document validation documentation.

Visual Studio XML schema selection dialog

Visual Studio XML schema selection dialog

If you are using Visual Studio Code to edit your policy XML, you can use one of several extensions from the Visual Studio Marketplace to help you work with the XML. Similarly, the XML Tools plugin is available for the Notepad++ editor. This is not meant to be an exhaustive list of editors you can use for XML files – just a few that are top-of-mind.

NOTE
As of the time of this writing, version 0.3.0.0 of the schema has not been updated since April 2017 (almost 3 years.) Since then there have actually been changes to the policy schema, and a few elements have been added that the schema will incorrectly flag as not being legal elements. Until the schema is updated, use it for guidance and to help a bit with your typing. If you find that something you believe to be correct is being improperly flagged, check it against the online documentation to be certain.

Establishing the Policy’s Building Blocks

Since we’re leaving the coverage of the BasePolicy element until a later post in this series, we will start our discussion with the Building Blocks section of the policy file. The Building Blocks section can be considered the “specification” section of the policy, and mostly contains core declarations that need to be referenced by the “functions” later in the policy.

The BuildingBlocks element might contain the following child elements:

Element Purpose
ClaimsSchema Declares any claims that will be referenced by the “functions” within the policy.
ClaimsTransformations Specifies small operations that are used by policy “functions” to set the value of one claim by performing a specified method with the values of a set of input parameters and/or other claims. If the Technical Profiles being discussed later are the “functions” of a policy, these might be (and it may perhaps be a bit of a stretch to say so) the “operations”.
ContentDefinitions Defines HTML templates that contain styling and other customizations that will be applied to the user interface.
Predicate and PredicateValidations Configures small operations that can be attached to claims to provide data validation.
Localization Specifies locale-specific language elements.
DisplayControls Currently in Preview. Configures user interface controls that are able to interact with backend functionality before completing the “function” that displays them. The current controls alternative can trigger operations, but normally this happens when you click the button that completes the dialog.

The Role of Claims in a Custom Policy

It is important to first understand how the claims – the variables – are used when a policy is executed by the Identity Experience Framework (IEF) runtime. As the steps defined in the UserJourney section are executed, data is stored in memory in a so-called claims-bag. Operations take claims as inputs by retrieving them from the claims-bag and emit claims as outputs by adding or updating them in the claims-bag. The whole process concludes with a set of claims being retrieved form the claims-bag and then returned from the policy in the form of a token. This sequence is illustrated below:

The Claims-Bag and the flow of claims in the IEF
The claims-bag and the flow of claims in the IEF

NOTE
This illustration is derived from the article Identity and Claims Exchange from the Identity Experience Framework Wiki, currently maintained by Microsoft’s Jas Suri. There is a lot of great information contained in both the wiki and Jas’s GitHub repositories. If you are following this series to learn more about AAD B2C custom policies, it is definitely worth checking that content out as well!

Declaring Claims

We will continue working with the example policy that we have developed throughout the previous posts in this series. You can download a copy of that file here, being sure to update the TenantId, PolicyId, and PublicPolicyUri attributes to the correct values from your AAD B2C tenant, and (if you used different names) replacing your own cryptographic key names in the JWTIssuer TechnicalProfile element.

Before a claim in a policy can be used in a User Journey, it must first be declared. Claims are declared with ClaimType elements within the Building Blocks’ ClaimsSchema section.

Our example policy has declared two claim so far – objectId and message. These have been copied below for reference. The declarations are minimal, just specifying the claim’s names and data types. There is actually a lot more that can be specified.

<ClaimsSchema>
  <ClaimType Id="objectId">
    <DataType>string</DataType>
  </ClaimType>
  
  <ClaimType Id="message">
    <DataType>string</DataType>
  </ClaimType>
</ClaimsSchema>

Add the following Claim Type declarations into the ClaimsSchema section of the policy file, immediately after the declaration of the message claim:

<ClaimType Id="givenName">
  <DisplayName>First Name</DisplayName>
  <DataType>string</DataType>
  <DefaultPartnerClaimTypes>
    <Protocol Name="OAuth2" PartnerClaimType="given_name" />
    <Protocol Name="OpenIdConnect" PartnerClaimType="given_name" />
    <Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" />
  </DefaultPartnerClaimTypes>
  <UserHelpText>Your given name (also known as first name).</UserHelpText>
  <UserInputType>TextBox</UserInputType>
</ClaimType>

<ClaimType Id="surname">
  <DisplayName>Last Name</DisplayName>
  <DataType>string</DataType>
  <DefaultPartnerClaimTypes>
    <Protocol Name="OAuth2" PartnerClaimType="family_name" />
    <Protocol Name="OpenIdConnect" PartnerClaimType="family_name" />
    <Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" />
  </DefaultPartnerClaimTypes>
  <UserHelpText>Your surname (also known as family name or last name).</UserHelpText>
  <UserInputType>TextBox</UserInputType>
</ClaimType>

<ClaimType Id="displayName">
  <DataType>string</DataType>
  <DefaultPartnerClaimTypes>
    <Protocol Name="OAuth2" PartnerClaimType="unique_name" />
    <Protocol Name="OpenIdConnect" PartnerClaimType="name" />
    <Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" />
  </DefaultPartnerClaimTypes>
</ClaimType>

This markup declares 3 additional Claim Types – givenName, surname, and displayName – and each includes more information than the original objectId or message declarations did.

NOTE
As was the case with the objectId claim, the givenName, surname, and displayName claim IDs actually correspond to the names used by the underlying AAD database which will eventually be used to store this information. We have chosen to use those claim IDs here to keep things simpler later.

All of the claims declared so far have been had a DataType value of string. There are several other data types that can be used to declare custom policy claims:

Data Type Content
string Text values up to 256 characters
boolean True/False values
int 32-bit signed integer
long 64-bit signed integer
date Calendar-date values, formatted as yyyy-MM-dd
dateTime Represents a specific time, formatted as yyyy-MM-dd HH:mm:ssZ
phoneNumber Currently in preview. Represents a phone number to be used for Mmulti-factor authentication integration
stringCollection A group of text values, used for variables that can hold more than one value at a time

Notice that the declarations for givenName and surname each include a DisplayName element. This value will be used by the IEF runtime to generate the corresponding user-facing label when the claim is shown in a user interface.

The givenName and surname claims also include UserHelpText and UserInputType elements. These elements are also used to control how the claim is displayed to the users in the policy’s user interface, and are only included with givenName and surname since these are the only two claims that we intend to display in a user interface. User Help Text is just that – text that you can provide to be displayed to the users in the policy’s user interface in order to help them with their data entry. You may want to provide help text for any data size or content restrictions, for example. User Input Type dictates the actual kind of control that will be displayed in the user interface. The table below shows the current list of available User Input Types:

User Input Type Purpose
TextBox Single-line text box
EmailBox Basic email input
Password Password entry – masks text as the user enters it
DateTimeDropdown Provides Day/Month/Year dropdowns
RadioSingleSelect Shows radio buttons for each Enumeration element included within a claim’s Restriction section (Restrictions will be discussed in a later post)
DropDownSingleSelect Provides a dropdown list that allows a single element to be selected from the set of Enumeration elements included within a claim’s Restriction section
CheckBoxMultiSelect Displays a collection of checkboxes for each Enumeration element included within a claim’s Restriction section, allowing multiple selection. Selections are returned in the string claim as a comma-separated list.
Readonly Displays a text box in read-only mode containing the claim’s value
Paragraph Displays the claim value as read-only text within an HTML <p></p> block

NOTE
Notice that the Claim Type declaration includes both the data type and the user-interface display element to be used for the claim. This is a bit of a departure from most programming languages that you may be used to. It sets up an interesting problem if you are going to be displaying a value in more than one way on different user interfaces. What you end up having to do is to declare claims per-display-need, and use Claims Transformations (discussed below) to copy values between claims.

All three declarations include a DefaultPartnerClaimTypes element containing Protocol entries for OAuth, OpenIDConnect, and SAML2. If you recall from the previous post in this series, the PartnerClaimType attribute was used in the OutputClaim element Relying Party Technical Profile in order to specify a different name when outputting the objectId claim. In order to align the name given to a claim within the policy to the name it was required to have for interaction with a given protocol. In this case, the claims declarations themselves include identifiers that should be used by default when the policy interacts with each of the corresponding protocols.

Claims Transformations

Claims Transformations are small operations that are used by the policy “functions” to set the value of a claim by performing an operation with the given input parameters and/or input claims. Each transformation specifies the desired operation to be performed via a TransformationMethod attribute.

Add the following Claims Transformations element declaration into the BuildingBlocks section of the policy file, immediately after closing tag of the ClaimTypes element.

<ClaimsTransformations>
  <ClaimsTransformation Id="GenerateObjectIdTransformation" TransformationMethod="CreateRandomString">
    <InputParameters>
      <InputParameter Id="randomGeneratorType" DataType="string" Value="GUID"/>
    </InputParameters>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="objectId" TransformationClaimType="outputClaim"/>
    </OutputClaims>
  </ClaimsTransformation>

  <ClaimsTransformation Id="CreateDisplayNameTransformation" TransformationMethod="FormatStringMultipleClaims">
    <InputClaims>
      <InputClaim ClaimTypeReferenceId="givenName" TransformationClaimType="inputClaim1"/>
      <InputClaim ClaimTypeReferenceId="surname" TransformationClaimType="inputClaim2"/>
    </InputClaims>
    <InputParameters>
      <InputParameter Id="stringFormat" DataType="string" Value="{0} {1}"/>
    </InputParameters>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="displayName" TransformationClaimType="outputClaim"/>
    </OutputClaims>
  </ClaimsTransformation>

  <ClaimsTransformation Id="CreateMessageTransformation" TransformationMethod="FormatStringClaim">
    <InputClaims>
      <InputClaim ClaimTypeReferenceId="displayName" TransformationClaimType="inputClaim"/>
    </InputClaims>
    <InputParameters>
      <InputParameter Id="stringFormat" DataType="string" Value="Hello {0}"/>
    </InputParameters>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="message" TransformationClaimType="outputClaim"/>
    </OutputClaims>
  </ClaimsTransformation>
</ClaimsTransformations>

This markup declares three Claims Transformations – GenerateObjectIdTransformation, CreateDisplayNameTransformation, and CreateMessageTransformation. The GenerateObjectIdTransformation specifies in its TransformationMethod attribute that the transformation uses the CreateRandomString method, which is used to update a string claim with a random value – in this case a GUID – as specified by the value of the randomGeneratorType attribute in the InputParameter entry that is required by this method. The claim that is updated with the new value is specified in the OutputClaims section.

Each transformation method indicates specific names that must be given for its input and output parameters. For InputParameter elements, the name is specified in the Id attribute. For outputs, the name is specified by the TransformationClaimType attribute of an OutputClaim element.

The remaining two Claims Transformations are described below:

  • The CreateDisplayNameTransformation transformation indicates a Transformation Method of FormatStringMultipleClaims – it takes an input parameter that specifies a format string which expects 2 inputs and two input claims that are substituted into the format string to produce the indicated output claim.
  • The CreateMessageTransformation transformation indicates a Transformation Method of FormatStringClaim – it takes an input parameter that specifies a format string which expects 1 input and one claim that is substituted into the format string to produce the indicated output claim.

There are dozens of different transformation methods available for you to use. You can find their definitions grouped by data type at https://docs.microsoft.com/en-us/azure/active-directory-b2c/claimstransformations.

NOTE
You may notice that it is possible to simply use a single Claims Transformation to create the personalized greeting message without first setting an intermediate displayName transformation. In a later post in this series, we will want to be able to work with a displayName by itself, so we’re using two distinct transformations to accomplish both tasks here in order to make things simpler later.

Content Definitions

The next Building Block element that you need to consider is the Content Definition. Content Definitions allow you to specify the HTML templates that govern the overall layout for the page(s) that will be displayed to users of you policy. When your policy is run, the elements specified in the Content Definition will be merged with the specific user interface elements for the claims that are being displayed in order to realize the final page content.

A Content Definition is specified with the ContentDefinition element and an accompanying Id attribute. Within the element, you need to specify the nature of the data that will be displayed as well as the HTML template that will be used.

Data URIs

The nature of the data that will be displayed on pages that use this Content Definition is specified with the DataUri child element within the ContentDefinition element. The Data URI guides the content that the AAD B2C runtime uses to blend the page template and individual user interface elements into a final HTML page, and the key element that guides this process is the page identifier. There are currently seven named page identifiers that AAD B2C supports:

Page Identifier Purpose
idpselection Lists identity providers that can be selected when a user is signing in. (Note – this is being replaced by the providerselection identifier below.)
providerselection Lists identity providers that can be selected when a user is signing in. Used for page layout version 1.2 or newer (page layout versions will be discussed shortly.)
unifiedssp Used to display a local account sign-in page that offers users the ability to instead invoke a sign-up process. This “SSP” version includes “keep me signed in” and “forgot your password” page elements when displayed.
unifiedssd Used to display a local account sign-in page that offers users the ability to instead invoke a sign-up process. This “SSD” version omits the “keep me signed in” and “forgot your password” page elements.
multifactor Used to display a pre-built multi-factor authentication (MFA) page.
globalexception Used for the page that is displayed when unhandled errors occur.
selfasserted The Data URI to use for any page that does not have one of the predetermined purposes listed above. This is the Data URI that you will use most commonly for your own page definitions.

Data URIs and Page Layout Versions

When using HTML, CSS, and other tools to present a customized look and feel for your pages, it becomes important to be certain that the elements that AAD B2C will be inserting into the page will be predictable and consistent. Otherwise you would have a hard time trusting that your CSS selectors would consistently work. However, AAD B2C and the IEF platforms continue to evolve, including making updating to the elements being rendered into a page. So how can you ensure the page consistency that you need? The answer lies in Page Layout Versioning. The version part of a Data URI ensures that the content that is being displayed will be consistent.

The original Data URI declarations had the formaturn:com:microsoft:aad:b2c:elements:page-name:version, where you specify the page-name and version portions. This format is being revised for newer page contracts, and now looks like: urn:com:microsoft:aad:b2c:elements:contract:page-name:version. Consider the following declarations:

Original Self-Asserted Data URI (Version 1.0) New Self-Asserted Data URI (Version 1.2)
urn:com:microsoft:aad:b2c:elements:selfasserted:1.0.0 urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.2.0

You can track what features have changed across page layout versions by using the AAD B2C documentation’s Page layout versions page.

IMPORTANT
It is important to note that if you want to use JavaScript within your custom pages, you need to use one of the new Data URI declarations (the ones that include “contract”) in your custom policy. In general, you will likely want to consider using the latest Data URIs in the Content Definitions in your policies for any new work that you are doing.

Load URIs

The Load URI element is where you specify the HTML template page that will be used. Azure AD B2C currently defines 3 built-in template styles that you can use – classic, Ocean Blue, and Slate Gray. Alternatively you can supply your own HTML (customizing your page layout HTML is a topic for another post on another day.) You can read more about these templates and custom HTML/CSS in the Azure AD B2C documentation.

To go along with the Data URI’s, AAD B2C also includes several default template page URIs, depending on the style you want to use, or you can define your own template page and use your own (absolute) URL:

Page Identifier Pages
idpselection
or
providerselection
Classic: ~/tenant/default/idpSelector.cshtml
Ocean Blue: ~/tenant/templates/AzureBlue/idpSelector.cshtml
Slate Gray: ~/tenant/templates/MSA/idpSelector.cshtml
unifiedssp
or
unifiedssd
Classic: ~/tenant/default/unified.cshtml
Ocean Blue: ~/tenant/templates/AzureBlue/unified.cshtml
Slate Gray: ~/tenant/templates/MSA/unified.cshtml
multifactor Classic: ~/tenant/default/multifactor-1.0.0.cshtml
Ocean Blue: ~/tenant/templates/AzureBlue/multifactor-1.0.0.cshtml
Slate Gray: ~/tenant/templates/MSA/multifactor-1.0.0.cshtml
globalexception Classic: ~/tenant/default/exception.cshtml
Ocean Blue: ~/tenant/templates/AzureBlue/exception.cshtml
Slate Gray: ~/tenant/templates/MSA/exception.cshtml
selfasserted Classic: ~/tenant/default/selfasserted.cshtml
Ocean Blue: ~/tenant/templates/AzureBlue/selfasserted.cshtml
Slate Gray: ~/tenant/templates/MSA/selfasserted.cshtml

You can find the HTML and CSS files used for the Ocean Blue and Slate Gray template definitions at https://docs.microsoft.com/en-us/samples/azure-samples/azure-ad-b2c-page-templates/azure-ad-b2c-page-templates.

Adding the Content Definition for our policy

Add the following Content Definitions element declaration into the BuildingBlocks section of the policy file, immediately after closing tag of the ClaimsTransformations element.

<ContentDefinitions>
  <ContentDefinition Id="SelfAssertedContentDefinition">
    <LoadUri>~/tenant/default/selfAsserted.cshtml</LoadUri>
    <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
    <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.2.0</DataUri>
  </ContentDefinition>
</ContentDefinitions>

We are declaring a Content Definition with an ID of SelfAssertedContentDefinition. It is using the built-in Classic self-asserted page template with the self-asserted version 1.2 Data URI. The RecoveryUri element is also specified – this is a required element and should just be left as is.

Functions in the Program – Technical Profiles

Now that we’ve declared some “variables” (Claims), Claims Transformations, and Content definitions, the next thing to do is to add some “functions” to actually do work with these artifacts. The role of “functions” in a policy is handled by TechnicalProfile elements, which are contained within Claims Providers.

The ClaimsProviders and ClaimsProvider Elements

Technical Profile elements are specified within ClaimsProvider elements in the ClaimsProviders section of the TrustFrameworkPolicy. As the name Claims Provider implies, most Technical Profiles accept input claims and some configuration data, and return (or provide) a set of output claims.

Add the following empty ClaimsProvider section into the policy’s existing ClaimsProviders element:

<ClaimsProvider>
  <!--
  The technical profiles defined in this section are used to generate values for claims that
  are being collected as part of the user journey.
  -->
  <DisplayName>Claim Generator Technical Profiles</DisplayName>
</ClaimsProvider>

The ClaimsProviders element can contain several ClaimsProvider elements, each one of which can contain multiple TechnicalProfile elements. This allows you to keep things organized by grouping functions with similar purposes together in the same Claims Provider.

Types of TechnicalProfile Elements

IEF defines a limited set of technical profile types that you can include in an AAD B2C custom policy. Put another way, there are only certain functions that can be executed from within a policy, although new profile types are being added over time. As of the time of this writing, these include:

Technical Profile Type Purpose
Claims Transformation Alters or validates the values of claims.
Self-Asserted Shows user interface elements to the end-user to display and/or collect claims values.
REST Makes a call to the REST endpoint defined for an external service in order to process and/or to retrieve some claims values.
Azure Active Directory Performs storage and retrieval operations against the underlying AAD user store.
OAuth1
OAuth2
OpenID Connect
SAML2
Connects with identity providers that implement the indicated protocol.
Session Management Coordinates storing and retrieving claims across user sessions.
JWT Token Issuer Constructs a JWT token that is returned by an AAD B2C policy.
One-time Password Either generates a code that can be sent to a user outside of AAD B2C (for example, via email) for them to to then enter in a UI element, or validates that the code that a user has provided matches teh code that was generated for them.
Phone Factor Currently in Preview. Provides phone-based multi-factor authentication (SMS or Phone Call). Renders its own UI page.
Azure MFA Currently in Preview. Provides phone-based multi-factor authentication (SMS only). You supply the UI.
Azure Application Insights Currently in Preview. Supports logging usage information to Azure Application Insights.

NOTE
Since one of the profile types allows you to make REST calls, you can implement and call your own API endpoint if you find that you need more functionality than what is offered by the exisitng Technical Profile types.

Technical Profile Execution

When a Technical Profile is executed by the AAD B2C IEF runtime, it actually flows through a prescribed sequence of steps. This sequence is illustrated below (NOTE – This illustration is derived from the AAD B2C Custom Policy Technical Profile documentation):

Execution Flow of a Technical Profile

Execution Flow of a Technical Profile

These steps include:

  • Retrieving session state claims (if any) for the technical profile and storing them in the claims-bag.
  • Applying any specified Input Claims Transformations. The necessary claims are retrieved from the claims-bag, transformations are applied, and the result of the transformation is placed back into the claims-bag.
  • Retrieving any additional claims required by the Technical Profile from the claims-bag.
  • Actually execute the Technical Profile’s core function.
  • Apply any declared Validation Technical Profiles (we will discuss these in the next post in this series.) These can only be declared for Self Asserted Technical Profiles.
  • Send any declared Output Claims to the claims-bag.
  • Apply any specified Output Claims Transformations. Like Input Claims Transformations, the necessary claims are retrieved from the claims-bag, transformations are applied, and the result of the transformation is placed back into the claims-bag.
  • Write any declared session state claims for the technical profile from the claims-bag.

Now that we have a high-level view of how a Technical Profile works, let’s see how we can make use of some of them to create our personalized greeting.

Claims Transformation Technical Profiles

We will start our discussion of the TechnicalProfile element by focusing on just one kind of Technical Profile – the Claims Transformation Technical Profile type. As the name implies, the Claims Transformation Technical Profile is commonly used to apply Claims Transformation operations that are defined in the policy’s BuildingBlocks section. The Claims Transformation Technical Profile type itself does not really have a Protocol Execution step as illustrated above. It merely runs through the Input and Output Claims Transformations portions of the sequence, with the Protocol Execution step effectively being a no-op.

Making the objectId Claim Value Random

Add the following TechnicalProfiles content to the otherwise empty ClaimsProvider element that you just added to the policy file, immediately following the DisplayName element that it contains:

<TechnicalProfiles>
  <TechnicalProfile Id="RandomObjectIdClaimGenerator">
    <!--
    In order for an Object ID to be returned (this is required in an interactive profile) one of the journey steps must include it as an output claim. 
    In this case, it is being done as a claims transformation without any input, but instead is generating a random value using a claims transformation.
    -->
    <DisplayName>Random Object ID Claim Generator Technical Profile</DisplayName>
    <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="objectId"/>
    </OutputClaims>
    <OutputClaimsTransformations>
      <OutputClaimsTransformation ReferenceId="GenerateObjectIdTransformation"/>
    </OutputClaimsTransformations>
  </TechnicalProfile>
</TechnicalProfiles>

This Technical Profile has an ID of RandomObjectIdClaimGenerator and it includes a DisplayName element which is required for all Technical Profiles. The type of the Technical Profile – and therefore its functionality – is set by including the required Protocol element. For a Claims Transformation technical profile, you specify that the Name of the protocol is Proprietary and its Handler attribute is set to Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null. To find the correct Name and (if required) Handler values, be sure to check out the documentation for the specific Technical Profile typethat you are using.

NOTE
You may recognize the Handler value as the fully qualified name of a .NET assembly. Several of these assemblies are used by the IEF to provide different functionality to the Technical Profiles. These assemblies are maintained and managed by the IEF runtime, and as of this writing are do not appear to be available publicly.

This Technical Profile also includes an OutputClaims section with a reference to the objectId claim. Claims Transformation Technical Profiles must include at least one Output Claim.

The final element in our RandomObjectIdClaimGenerator Technical Profile is an OutputClaimsTransformation declaration within the OutputClaimsTransformations element. This Output Claims Transformation includes a ReferenceId attribute that references the GenerateObjectIdTransformation ID of the Claims Transformation was declared earlier in the policy’s Building Blocks section. In this case, the Claims Transformation replaced the objectId claim’s value with a random GUID.

NOTE
It is actually possible – and sometimes quite useful – to create a Claims Transformation Technical Profile that doesn’t even include any Claims Transformations, but instead just includes one or more OutputClaim elements with DefaultValue attributes used to set the claim value. We saw something similar to this in the Technical Profile that was included in the RelyingParty element in the “Hello World!” policy in the previous post in this series, where the message claim was set merely by applying a default value to an Output Claim.

Setting the displayName and message Claim Values

Now add the following Technical Profile to the policy, inside of the same TechnicalProfiles element that was included with the RandomObjectIdClaimGenerator Technical Profile.

<TechnicalProfile Id="UserInputMessageClaimGenerator">
  <!--
  Generates the content of the message claim by applying the relevant claims transformation
  -->
  <DisplayName>Display Name Claim Generator Technical Profile</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="displayName"/>
    <OutputClaim ClaimTypeReferenceId="message"/>
  </OutputClaims>
  <OutputClaimsTransformations>
    <OutputClaimsTransformation ReferenceId="CreateDisplayNameTransformation"/>
    <OutputClaimsTransformation ReferenceId="CreateMessageTransformation"/>
  </OutputClaimsTransformations>
</TechnicalProfile>

The Technical Profile declared above has an ID of UserInputMessageClaimGenerator, and is similar to the RandomObjectIdClaimGenerator Technical Profile that precedes it. In this case, the new Technical Profile outputs the displayName and message claims, and invokes a pair of Claims Transformations to populate those claims. It first calls the CreateDisplayNameTransformation transformation to set the displayName value, and then calls the CreateMessageTransformation transformation to use that displayName value to set the personalized greeting in our message Output Claim.

NOTE
Both the objectId and message claims could have been output as the result of a single Technical Profile that included two OutputClaim entries instead of using two Technical Profiles to do so. In later posts in this series, the functionality used to populate these claims will take different paths. They are being kept separate now to make things easier to deal with then.

Collecting User Input

The Claims Exchange Technical Profiles used above set our personalized greeting message based on the user’s input, but we have not yet collected that input. So the next step is to create the Technical Profile that will allow our user to enter their name.

Collecting user input requires the use of a Self-Asserted Technical Profile. These Technical Profiles work with Display Claims, Input Claims, Output Claims, and Metadata elements to present user interface elements and to interactively collect information from users.

Displaying and Collecting Claims in the Technical Profile

Display Claims are specified with the DisplayClaims element, which contains a collection of DisplayClaim items. Each Display Claim contains a reference to one of the Claim Types declared in the Building Blocks section of the policy and indicates that the user interface element specified in the claim’s User Interface Type should be shown on the page being displayed by the Technical Profile. If you want to require users to provide a value for a Display Claim, set its Required attribute to true.

Input Claims are used to provide initial values for claims being displayed a Display Claims. If the claim has been set in the claims-bag, providing an Input Claim entry that references the claim will include the claims Claim Bag value in the control that is displayed for it when the user interface element is displayed. If the claim has not been set in the claims-bag, including it in the InputClaims collection and setting its DefaultValue attribute will also provide the initial value for the UI element.

Finally, Output Claims are used to determine which values are returned from the Technical Profile and are stored into the policy execution’s claims-bag.

Self-Asserted Technical Profiles also support including InputClaimsTransformations and OutputClaimsTransformations. The transformations will be invoked before the Input Claims are retrieved from the claims-bag or after the Output Claims are written to the claims-bag, respectively.

NOTE
Display Claims are a fairly recent addition to AAD B2C Custom Policies, and in fact they are currently in Preview. As such, there are a few things to keep in mind. First, the XSD schema is not aware of them and they will be flagged if you are using schema validation tools to edit your policies. Second, up until the introduction of Display Claims, Output Claims did double duty – they specified the claims that would be shown in the user interface and also indicated the claims to be stored in the claims-bag. Finally, when we discuss policy file hierarchies later in this series it will be important to revisit Display Claims and understand what you need to do to have them interoperate properly with related claims files which may still be using Output Claims to govern the display of user interface elements. With all of that having been said, Display Claims are being shown here because all indications are that this is the preferred mechanism to use going forward, where the items to be displayed and the items to be saved are kept in distinct specifications. In a later post in this series we will discuss a special type of Display Claim value – the Display Control – and how it can be used for input validation.

Technical Profile Metadata

In addition to working with Display, Input, and Output Claims, Self-Asserted Technical Profiles also work with elements declared in the Technical Profile’s Metadata element. The Metadata element is used to provide a Technical Profile with static values defined in the profile XML itself (or semi-static values defined when the profile is run by use of what are known as Claim Resolvers, which will be discussed in a later post in this series) rather than variable values that are extracted from the claims-bag. Each Technical Profile type defines a different set of both required and optional Metadata elements, which can be found in the documentation for the specific Technical Profile type.

Self-Asserted Technical Profiles require that you set the ContentDefinitionReferenceId Metadata value. This value should indicate the ID of a Content Definition that has been specified in the policy’s Building Blocks section, and tells the Self-Asserted Technical Profile how to render this page.

There are several other Metadata values that can be set for Self-Asserted Technical Profiles. Some of these are only valid for particular Page Layout types and versions. To see the other values that can be set, see the Self-Asserted Technical Profile Metadata section in the online documentation.

Updating Your Policy

Add the following Self-Asserted Claims Provider to your policy after the Claims Provider that contains the Claims Transformation Technical Profiles:

<ClaimsProvider>
  <!--
  The technical profile defined in this section allows a user to enter their first and last name, then composes the message text based on those entries.
  -->
  <DisplayName>Sample User Input Collection Technical Profiles</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="UserInformationCollector">
      <DisplayName>Collect Sample User Input Technical Profile</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <Metadata>
        <Item Key="ContentDefinitionReferenceId">SelfAssertedContentDefinition</Item>
      </Metadata>
      <DisplayClaims>
        <DisplayClaim ClaimTypeReferenceId="givenName" Required="true"/>
        <DisplayClaim ClaimTypeReferenceId="surname"  Required="true"/>
      </DisplayClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="givenName"/>
        <OutputClaim ClaimTypeReferenceId="surname"/>
      </OutputClaims>
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

The Technical Profile is set to the Self-Asserted type through the Protocol element, which sets the Name to Proprietary and the Handler to the SelfAssertedAttributeProvider assembly. The Metadata section sets the ContentDefinitionReferenceId value to reference the SelfAssertedContentDefinition you created earlier.

The Technical Profile specifies two DisplayClaim elements, one each for the givenName and surname claims. Both claims are marked as Required in order to compel the user to provide these values before completing the page. Finally two OutputClaim elements are included which store the values for the givenName and surname claims into the policy’s claims-bag.

Other Technical Profiles We Have Used So Far

In the previous post in this series you included an instance of the JWT Token Issuer Technical Profile. Unlike the Claims Exchange and Self-Asserted Technical Profiles we just discussed, it has the unusual Protocol value of None, and an OutputTokenFormat value set to JWT. It also differs from those Technical Profiles because it does not use any input or output claims. However it does require setting parameters through both the Metadata and CryptographicKeys elements. The JwtIssuer Technical Profile is reproduced below for reference:

<TechnicalProfile Id="JwtIssuer">
  <DisplayName>JWT Issuer</DisplayName>
  <Protocol Name="None" />
  <OutputTokenFormat>JWT</OutputTokenFormat>
  <Metadata>
    <Item Key="client_id">{service:te}</Item>
    <Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
    <Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
  </Metadata>
  <CryptographicKeys>
    <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
    <Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
  </CryptographicKeys>
  <InputClaims />
  <OutputClaims />
</TechnicalProfile>

You have already seen the Metadata element used in the Self-Asserted Technical Profile. The CryptographicKeys element is used to provide a Technical Profile with a reference to one or more Policy Keys stored alongside your policy in your AAD B2C instance. These Policy Keys can be used for digital signature and encryption operations, as well as recording application secrets such as token access keys. AAD B2C supports generating keys for you or allowing you to specify the keys yourself.

NOTE
There is actually one more Claims Provider Technical Profile that you have already seen. In the first post in this series you included a required Technical Profile with an ID of TpEngine_c3bd4fe2-1775-4013-b91d-35f16d377d13. This was a special Technical Profile, and its behavior is a little different from that of the Technical Profiles you will use to implement your own policy logic.

Sequencing the Steps of the User Journey

Now that we have some functions, it stands to reason that perhaps we’d like something to invoke them. The steps that policy execution will follow need to be specified in the UserJourneys section. Think of this section is saying “this is the journey that I want my user to take when they invoke my policy.” The UserJourneys element can contain one or more UserJourney child elements, with each User Journey describing a different user activity, such as sign-in, sign-up, reset-password, edit-profile, and so forth. It is more common to see multiple user journeys specified in a profile when you use a hierarchy of profiles, something we will discuss later in this series.

Each UserJourney element must have a unique ID assigned to it, which is how the journey is later referenced. Individual steps in each User Journey are specified in an OrchestrationSteps element which contains one or more individual OchestrationStep elements. Orchestration Step elements then contain attributes that describe the execution order (starting with “1”) of each step and the type of step being invoked. Depending on the type of Orchestration Step being used, more information may be required.

An OrchestrationStep element can also include a set of preconditions that can be evaluated to determine if the step should be skipped. We will discuss preconditions later in this series in the Hello! Please Either Sign In or Sign Up post.

Calling your Technical Profiles in the User Journey

Replace the existing contents of the HelloWorldJourney User Journey with the set of Orchestration Steps below:

<OrchestrationSteps>
  <OrchestrationStep Order="1" Type="ClaimsExchange">
    <ClaimsExchanges>
      <ClaimsExchange Id="GetObjectIdClaimsExchange" TechnicalProfileReferenceId="RandomObjectIdClaimGenerator" />
    </ClaimsExchanges>
  </OrchestrationStep>
  <OrchestrationStep Order="2" Type="ClaimsExchange">
    <ClaimsExchanges>
      <ClaimsExchange Id="GetUserInformationClaimsExchange" TechnicalProfileReferenceId="UserInformationCollector" />
    </ClaimsExchanges>
  </OrchestrationStep>
  <OrchestrationStep Order="3" Type="ClaimsExchange">
    <ClaimsExchanges>
      <ClaimsExchange Id="GetMessageClaimsExchange" TechnicalProfileReferenceId="UserInputMessageClaimGenerator"/>
    </ClaimsExchanges>
  </OrchestrationStep>
  <OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>

The HelloWorldJourney now contains four steps, three of which have a Type value of ClaimsExchange. The Claims Exchange step type is used for most step types, with a few exceptions that we will discuss later. When you specify a Claims Exchange Orchestration Step, you have to include a ClaimsExchanges element within the step. For a Claims Exchange step type, you need to include a single ClaimsExchange element (see the note below for an exceptions to this guidance) that has an ID of its own, as well as a reference to the ID of the TechnicalProfile element that you want to execute in this step.

NOTE
A ClaimsExchanges section can contain multiple ClaimsExchange elements when its OrchestrationStep is preceded immediately by an Orchestration Step that has a type of either CombinedSignInAndSignUp or ClaimsProviderSelection. These are specialized Orchestration Step types. We will see CombinedSignInAndSignUp used in the post Hello! Please Either Sign In or Sign Up later in this series. In those steps, you may be interested in presenting a user with the ability to use one of several different identity providers that you have configured, such as local accounts, social account providers, or custom OpenID Connect providers. When you do so, you will use the ClaimsProviderSelection element of an Orchestration Step to specify the available choices, and for each choice you will reference the ID of a ClaimsExchange element that needs to be specified in the next Orchestration Step.

Each of these Claims Exchange Orchestration Steps includes a TechnicalProfileReferenceId declaration that indicates the Technical Profile that the step should be executed. In this sequence, the RandomObjectIdClaimGenerator Technical Profile is executed, which sets the objectId claim. This is followed by the UserInformationCollector Technical Profile, which shows the user interface to collect the user’s name, and then the UserInputMessageClaimGeneratorTechnical Profile is called to compute the displayName and message claims.

Sending the JWT Token When the User Journey Completes

The final Orchestration Step to be invoked is the same SendClaims step that was included in the policy from the previous post in this series. Instead of being a Claims Exchange step, this step specifies a Type of SendClaims. The Send Claims Orchestration Step is responsible for returning a token that contains the required claims from the policy. It is unique in that instead of using a ClaimsExchange element to reference the Technical Profile that it calls, it instead uses an attribute called CpimIssuerTechnicalProfileReferenceId to reference the ID of the JWT Token Issuer Technical Profile that should be run.

Updating the Output Claims in the Relying Party

Replace the Output Claims block in the Relying Party section of the policy with the following:

<OutputClaims>
  <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
  <OutputClaim ClaimTypeReferenceId="displayName" />
  <OutputClaim ClaimTypeReferenceId="message" />
</OutputClaims>

There are two key things to notice with this change. First, we have removed the use of Default Values for setting the claims values. We could have left them there, but they are no longer necessary since our claims values are being set by the Technical Profiles in our User Journey. Second, we have added the displayName claim to the set of claims being added to the JWT token that will be produced by our policy.

Let’s Try It Out

It’s time once again to upload the updated policy file. As discussed in the previous post in this series, be sure to check Overwrite the custom policy if it already exists in the Upload custom policy dialog when you upload the new policy. Once the policy has been uploaded, click on the policy ID to display the custom policy panel, make sure the jwt.ms application is selected, and then click Run now to run the policy.

NOTE
In case you have not been able to follow along or your policy will not upload because of an error, you can download a copy of the completed file here, being sure to update the TenantId, PolicyId, and PublicPolicyUri attributes to the correct values from your AAD B2C tenant, and (if you used different names) replacing your own cryptographic key names in the JWTIssuer TechnicalProfile element.

When you run the policy, instead of immediately navigating to the jwt.ms site, you should see a user interface displayed in your browser that resembles the following:
Displaying the User Interface to Collect User Input

Displaying the user interface to collect user input

Enter your first name and last name in the boxes provided, then click Continue. Your browser should now navigate to the jwt.ms site and display the contents of the token created by running your profile. It should contain elements similar to ones shown below:

...
{
  ...
  "sub": "fbc528d8-9424-40ee-90dc-16b6dbe44b12",
  "name": "John Garland",
  "message": "Hello John Garland"
}
//(This content has been trimmed for brevity)

There are a few things to notice here. First of all, the sub claim no longer contains a text message as it did at the conclusion of the previous post in this series, but instead is set to a random GUID value. If you run the policy a few more times, you will see a different sub value displayed each time.

Second, the displayName claim has been included. But notice that the key in the JSON for the displayName claim has been set to name. If you go back and examine the declaration for the displayName claim in your policy definition, you will notice that it specifies a DefaultPartnerClaimTypes section, and includes a Protocol entry for the OpenIdConnect protocol. This entry specifies that when communicating with an OpenID Connect protocol, the label name should be used by default instead of displayName.

<ClaimType Id="displayName">
  <DataType>string</DataType>
  <DefaultPartnerClaimTypes>
    ...
    <Protocol Name="OpenIdConnect" PartnerClaimType="name" />
    ...
  </DefaultPartnerClaimTypes>
</ClaimType>  

Finally, the message has been changed to a now-personalized greeting.

It was quite the endeavor to get here, but there you have it! The policy can now say “Hello” to you by name. This post covered:

  • How to declare Claims
  • How to configure Claims Transformations that can manipulate those Claims
  • How to specify Page Layouts to use via Content Definitions
  • The role and execution flow of Technical Profiles
  • How to work with Claims Transformation Technical Profiles
  • How to present user interfaces with Self-Asserted Technical Profiles
  • How to invoke Technical Profiles in sequence through the use of Orchestration Steps

It took a while to get through it all, but we have covered a lot of foundational ground in this post, and the content we’ve seen here will set us up for the rest of the posts in this series.

This post introduced you to how you can collect user input with a custom policy. The next post in this series will take a deeper look at some of the techniques you can use to restrict, verify, and validate that user input.

Stay Informed

Sign up for the latest blogs, events, and insights.

We deliver solutions that accelerate the value of Azure.
Ready to experience the full power of Microsoft Azure?

Atmosera is thrilled to announce that we have been named GitHub AI Partner of the Year.

X