Sign Up or Sign In Users With Azure AD B2C: Part 1

Michael Collins
Neudesic Innovation
22 min readJan 16, 2022

--

Azure Active Directory B2C is a very powerful identity management solution for applications. In my previous posts, we set up an Azure Active Directory B2C tenant and set it up for customization. In this post, we’re going to start working with identities by enabling the ability for acquire new users and let users log in using a password to authenticate them. I will be introducing you to custom policies, the programming language of Azure Active Directory B2C, and we will build out our first user story.

This is the fourth post in a series documenting how to use Azure Active Directory B2C to implement a custom identity management solution for a Software-as-a-Service application. If you are new to this series, I recommend reviewing the previous posts in the series:

  1. Building Application Identity Solutions using Azure AD B2C
  2. Configure Azure AD B2C for Customization
  3. Understanding the B2C Directory

Everything Starts with Stories

In this post, we’re going to implement two features: signing up as a new user and logging into Azure AD B2C. I’m going to start by writing them up as user stories:

As a new user, I will create a user account to use Project Center to manage my project work.

As a user, I will log into Project Center in order to report my project status and update my tasks.

For the first story, I am setting the following acceptance criteria:

  • The user will provide:
  • username
  • password
  • email address
  • The user will confirm the password by entering it twice
  • both passwords must match
  • an error will be shown if the passwords are not identical
  • The user must validate their email by entering a code that is send to their email

For this series, I’m not going to be automating the tests. I’ll be running the tests manually.

For the second story, my acceptance criteria is:

  • The user can successfully log in using their username and password
  • An error will be shown if the username is incorrect
  • An error will be shown if the password is incorrect

This feels simple enough to achieve. It’s time to dive in.

The B2C Programming Model

Azure Active Directory B2C is not only just another cloud service for identity management. It can also be treated as a platform that can be fully customized. Customization is done using something named Custom Policies. A custom policy is an XML definition of a relying party policy that guides a user through a specific activity.

Let’s back up a step and talk about relying parties. What is a relying party? A relying party is a consumer of Azure Active Directory B2C and its identity management services. The relying party is typically an application such as a mobile application, desktop application, or web application. A relying party can be another server or service that consumes APIs that are protected by an Azure Active Directory B2C tenant. The relying party trusts that the B2C tenant has identified a user correctly and that the access token provided by B2C has the correct permission and identifying information for the user.

A custom policy in Azure AD B2C is also known as a relying party policy. A custom policy is used to implement an HTTPS endpoint that a relying party can use to perform an action. Some policies can be interactive with the user using a web browser such as for signing up for a user account, logging into an application, or changing a password. Other policies may be automated without any user feedback such as logging out of an application or a user choosing to delete their user account if they no longer wish to use the application.

Custom policies implement a user journey, which is a workflow of activities that are performed on the user’s behalf. During a user journey, the user may be shown a page to enter information on. For example, when logging in the user will be prompted for their username and password. Other activities that happen behind the scenes could include querying the directory for the user’s information and validating the user’s password. This is an example of a non-interactive activity.

All custom policies end with the issue of a new JWT token. In some cases, the JWT token may be invalid, such as a user deleting their own user account. But the end result of every custom policy is to produce a new token.

Claims

A B2C custom policy is just like any other program, except it is coded in XML. But just like in any other program, you are going to have to deal with data in custom policies. In order programming languages, we store data in variables. In a custom policy, variables are known as claims.

I really think that claims is a bad name for the data and they should be variables. When I think of claims, I think of the data that is returned in a JSON web token that provides an assertion of a user’s identity such as their email address or full name. Within the context of my custom policy, not every claim is going to be returned in the token. Some of the claims I will use for temporary storage or I will transform them within the custom policy, but they never actually become part of the JSON web token. So if it makes it easier, just think of claims right now as variables. We’ll talk about real claims that are included in the JSON web token output later.

Some examples of claims you may see in a typical custom policy include:

  • socialIdpUserId: username
  • tenantId: the Azure Active Directory B2C tenant identifier
  • objectId: the identifier of the user in Active Directory
  • password: the password that the user entered in the form
  • newPassword: the password for a new user or changed password
  • reenterPassword: the password used to confirm that a new password is correct
  • email: the user’s email address
  • displayName: the user’s display name or full name
  • newUser: true if the user signed up
  • givenName: the user’s given or first name
  • familyName: the user’s family name, surname, or last name

There is not a standard set of claims that a custom policy must use. You can define your own set and add new claims as necessary. When building custom policies for Azure AD B2C, the orchestration flows and user journeys that you will be building will initialize claims by collecting data from the user, reading claims from a data store or external service, transforming claims, persisting claims, or outputting claims as part of the final JSON web token.

Deconstructing the Sign Up or Sign In Flow

To get started with building our first relying party policy, we’re going to deconstruct a sample one provided by Microsoft. The Microsoft team behind Azure Active Directory B2C maintains a GitHub repository containing starter packs for Azure AD B2C. Since we are currently focusing on local accounts that authenticate users based on a username and password, we will start by decomposing the starter pack for local accounts, and then once we understand it, we will re-compose it, deploy it, and test it in our B2C tenant.

⚠️ In the official starter packs, you will notice that Microsoft uses a specific inheritance strategy to implement their custom policies. Custom policies can inherit from other custom policies. Custom policy inheritance chains can reach a maximum depth of 10 custom policies. As I decompose the starter pack in this post, I will be jumping around between the different policies to extract out the information that I need to understand the whole picture. When I re-compose the custom policy, I am not necessarily going to rebuild the four custom policy structure that Microsoft is using. I’m going to go top-down in this series and will be refactoring and introducing inheritance where it makes sense to. You are welcome to implement custom policies in whichever way makes sense to you.

⚠️ Another warning before I get started. I will be including small snippets of the XML for the custom policies as needed to explain and make sense of the standard custom policies, and I will do the same as I reconstruct my custom policy. I am not going to go into great detail to explain the XML schema. I will provide enough explanation for you to understand what I am seeing and doing in my implementation. I would suggest when building a custom policy that you keep the reference documentation open in a web browser.

I am going to start my deconstruction by looking at the SignUpOrSignin.xml file in GitHub. This policy implements the Sign Up or Sign In relying party policy. B2C will create an HTTPS endpoint for this policy that client applications can invoke and open in a web browser. This policy will display the web user interface for creating a new user or logging in an existing user and will output the access token that the user can use to invoke API operations on behalf of the user.

The relying party policy looks like this:

The first child element is a <DefaultUserJourney> element that references the SignUpOrSignIn user journey. We will come back to that to better understand it. Let’s first take a look at the <TechnicalProfile> element.

A technical profile is a way of performing an action. A technical profile in a custom policy could be considered similar to a function or a command in a programming language. Technical profiles accept inputs using claims and produces outputs to claims. Technical profiles can prompt the user for information, read from or write to the directory, or call an Azure Function or REST API.

In the <RelyingParty> block above, there is a single <TechnicalProfile> element. You can consider this technical profile to be the main action for the relying party policy, similar to a controller method in an MVC-type web application or the main function of a program. The id attribute of a <RelyingParty>’s <TechnicalProfile> element will always be PolicyProfile. This indicates that this <TechnicalProfile> implements the custom policy.

The <Protocol Name="OpenIdConnect" /> child element indicates that the technical profile implements the OpenID Connect protocol. This is important because the relying party policy is exposed through an HTTP endpoint, so Azure Active Directory B2C needs to know how to map the HTTP request to the custom policy. By declaring the OpenID Connect protocol, Azure Active Directory B2C knows how to parse and deal with the request, invoke the custom policy, and deal with the response. I will cover the OpenID Connect protocol in a later post as it is not fundamentally important to us yet. But if you want to read more about it, the specification can be found here.

The <OutputClaims> section indicates which claims are output and included in the JWT token that is produced by the custom policy. Remember that the point of custom policies is always to generate some kind of token. The default list of output claims specifies that the displayName, givenName, surname, email, objectId, and tenantId claims should be returned in the JWT token that is produced by the custom claim. You can add claims to or remove claims from the JWT by editing the list of claims in <OutputClaims>.

The <SubjectNamingInfo> element just specifies that the sub claim in the JWT will be used to identify the user. By default, the sub claim will contain the unique identifier for the user in the directory.

In summary, we have a relying party policy that implements the OpenID Connect HTTP protocol and outputs a JWT token containing some claims about the user. Now the actions that are performed to satisfy the HTTP request are implemented by the user journey. Our next step is to take a deep dive into the SignUpOrSignIn user journey specification.

Exploring User Journeys

At its simplest level, a user journey is a sequential pipeline of actions to execute to complete a task. A user journey is made up of one or more steps organized in a straight line. Imagine having ten squares ahead of you with kiosks at each square. You stop at the first square and perform the action required at the kiosk. When you are complete, you then move onto the next square. On occasion, you may determine that there is a problem and you need to stop and start over, so you stop and end your journey. You might also reach a square and the activity at that square is not relevant to you, so you can skip it. That is how user journeys work.

This is what the SignUpOrSignIn user journey looks like for the starter kit:

As you can see, the user journey has a <UserJourney> container element and multiple <OrchestrationStep> elements that describe each step of the journey. Each <OrchestrationStep> has an Order element that specifies where in the journey each step is performed.

The Type attribute specifies what kind of activity is performed by the <OrchestrationStep>. Orchestration steps can be used for different purposes. One orchestration step may display a form for the user to enter information. Another one may invoke a REST API. The orchestration step types are:

  • ClaimsProviderSelection: We will use this type later when we add support for social media and external identity providers. But when more than one authentication provider is enabled (including local authentication), the user will be provided with a list of the different identity providers and can choose an identity provider to use. If only one identity provider is specified, then this step is skipped.
  • CombinedSignInAndSignUp: This step type will present a web UI to the user to either sign in using a local account provider, select an external identity provider to authenticate with, or sign up for a new local account (using a password; no sign up is necessary for an external identity provider).
  • ClaimsExchange: This step type will exchange claims with another claims provider. For example, we may use a ClaimsExchange step to invoke an Azure Function or REST API to retrieve information. We might also use a ClaimsExchange step to read from or write to the directory.
  • GetClaims: The orchestration step should process claims sent to Azure AD B2C from the relying party using the <InputClaims> specification in the <RelyingParty> policy.
  • InvokeSubJourney: The orchestration step executes a child user journey and exchanges claims with that journey.
  • SendClaims: The orchestration step sends the claims to the relying party with a token issued by a claims issuer. This step will be used to build and generate the JWT token, for example.

Given this knowledge, let’s take a look at the SignUpOrSignIn user journey. In the above XML, we can see that the user journey has four orchestration steps:

  1. A CombinedSignInAndSignUp step that will display the login form to the user to enter a username or password, or choose to sign up for a new user account.
  2. A ClaimsExchange step to exchange claims with another provider (we haven’t explored what yet).
  3. Another ClaimsExchange step to exchange claims with another provider (also haven’t explored that yet).
  4. A SendClaims step to generate and send the JWT token back to the client application.

Let’s walk through and explore each step.

Step 1: CombinedSignInAndSignUp

Looking at the <OrchestrationStep> element for step 1, we see that it also has a ContentDefinitionReferenceId attribute set to api.signuporsignin. We know that this first step is of type CombinedSignInAndSignUp, so that will present the sign in UI to the user to enter a username and password to log in. The UI will also provide a link for the user to sign up for a user account. The ContentDefinitionReferenceId attribute. The documentation tells us that the value is the identifier of a content definition. What is a content definition?

A content definition points to an HTML 5 template for a self-asserted technical profile. A self-asserted technical profile is an action where the user is expected to provide input or supply the values for claims through a web form or interacting with a web UI.

A content definition is a type of building block, or a definition that custom policies is built to use. We briefly touched on claims, whose schema or definition is a building block. Building blocks are just reusable components for custom policies.

The api.signuporsignin content definition is defined as:

The <LoadUri> element contains the URL of the HTML 5 template that will be used to render the UI for logging the user in. We will take a deeper look at <LoadUri> in a future post on UI customization. For now, <LoadUri> points to a built-in template that is hosted by the Azure AD B2C HTTP endpoint.

The <DataUri> element is used to specify the page identifier. The page identifier loads and initiates UI elements and client-side JavaScript that renders the correct UI. The format of the value is urn:com:microsoft:aad:b2c:elements:page-name:version. The following page-name values are supported by Azure AD B2C:

  • globalexception: Displays an error page when an exception or error is encountered.
  • providerselecton or idpselection: Presents the list of identity providers that the user can choose to sign in with.
  • unifiedssp: Displays a form for signing in with a local account that is based on the user’s email address or username and password. The form also allows the user to sign up or reset their password if the password is forgotten or lost.
  • multifactor: Verifies telephone numbers by using text or voice during sign up or sign in.
  • selfasserted: Displays a form to collect data from a user. For example, a selfasserted UI could allow the user to edit their profile and update the directory.

For the api.signuporsignin profile, the <DataUri> element is set to urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.5, so this orchestration step will show the sign in form or give the user the option of signing up for a user account.

Moving back to the <OrchestrationStep> element, the first child element is the <ClaimsProviderSelections> element. The <ClaimsProviderSelections> element lists the identity providers that the user is allowed to choose from to identify themselves. If more than one identify provider is listed in this section, then the user will be presented with a list of providers to choose from. If only one identity provider is specified, that identity provider is selected automatically by default. The identity providers will be listed in the UI in the order that they appear in the <ClaimsProviderSelections> element. In our case, we only have the single local provider specified, so the user will not be able to choose from a list of options and will be forced to enter a username or email address and a password to authenticate.

The child <ClaimsProviderSelection> element can have one of two attributes. If TargetClaimsExchangeId is specified, then if that claims provider is selected, the user is advanced to the next step in the user journey and the <ClaimsExchange> element with the specified identifier is evaluated (more on <ClaimsExchange> shortly). If ValidationClaimsExchangeId is specified, then if that claims provider is selected, the specified <ClaimsExchange> element is evaluated within the current orchestration step to validate the user’s identity using the claims provided by the user through the UI.

In the starter pack’s user journey, the first step has a single <ClaimsProviderSelection> element with the ValidationClaimsExchangeId attribute set to LocalAccountSigninEmailExchange. The <ClaimsExchange> element with reference identifier LocalAccountSigninEmailExchange references the technical profile named SelfAsserted-LocalAccountSignin-Email. This means that when the user gets to the first step, the SelfAsserted-LocalAccountSignin-Email technical profile will be executed to collect the user’s username and password and authenticate the user.

The SelfAsserted-LocalAccountSignin-Email technical profile is defined as:

A technical profile is defined within a <ClaimsProvider> definition. A claims provider defines a party that the custom policy interacts with, and technical profiles are the mechanisms that are used to interact with the party. We used a technical profile earlier to implement the behavior of the relying party policy. When a technical profile runs, the following happens:

Execution flow for a technical profile
  1. The first action is to load the user’s state from the Azure AD B2C session. Azure Active Directory B2C user journeys are just like other HTTP services and are typically stateless. State between requests is persisted into a session object and stored between requests. The technical profile will retrieve the user’s current session and load the current state of the user’s claims. When starting the sign up or sign in user journey, the state will be empty because the user has not performed any actions yet or collected any state. There are many state providers available and the one that should be used by the technical profile is specified in the <UseTechnicalProfileForSessionManagement> child element.
  2. If any claims transformations are specified on input claims, the transformations are executed and the transformed values are added to the input claims for the technical profile’s execution.
  3. Input claims not built from transformation are retrieved from the claims bag and made available to the technical profile as input claims.
  4. The technical profile performs its action. This could involve exchanging claims with a REST API or displaying the sign in form.
  5. Any validation technical profiles are executed. Validation technical profiles are used to process the result of the technical profile execution. For example, in a sign in step, the validation technical profile can attempt to validate that the user’s password is correct.
  6. Claims that are output by the technical profile are merged into the custom policy’s claims bag.
  7. If any output claims transformations are specified, the transformations are performed.
  8. The data output by the technical profile are written to the designated session store and the state is updated.

The SelfAsserted-LocalAccountSignin-Email technical profile uses the SM-AAD technical profile for session management:

The <Protocol> element defines how the technical profile will talk with the external party. For the SM-AAD technical profile, it will use the default single sign-on session manager to retrieve or persist the session state.

The <PersistedClaim> elements define which data should be persisted to or read from the session state. These are values that may be used for precondition checks on subsequent steps or are needed by the application. The SM-AAD technical profile stores the following claims in the session state:

  • objectId
  • signInName
  • authenticationSource
  • identityProvider
  • newUser
  • executed-SelfAsserted-Input

The <OutputClaim> elements define any claims that should be read from the session provider. In the SM-AAD technical profile, only the objectIdFromSession claim is output and it is set to true by default.

Let’s go back to the earlier conversation on claims. The session provider technical profile is the first time in the user journey that we are coming across claims. Remember that claims are like variables in other programming languages and are used to store information about the user. Just like in other programming languages, you have to declare variables before you use them. In Azure Active Directory B2C, there is a building block called the claims schema where we define claims. The claim schema definitions for the above claims are shown here:

Given this, when the SelfAsserted-LocalAccountSignin-Email technical profile runs, it will retrieve these claims, if present (they won’t be) from the session provider.

The <Protocol> element of the SelfAsserted-LocalAccountSignin-Email technical profile indicates that the technical profile is self-asserted. This means that the user is expected to provide the information needed to initialize the claims. The self-asserted technical profile will display the UI that the user will use to enter their username and password.

The <Metadata> element provides values that tells the self-asserted technical profile what UI to show and customizes the behavior of the UI. The self-asserted technical profile supports the following metadata:

  • setting.operatingMode: This property controls the username field for login to indicate what the value of the field will be. The possible values are either Username or Email to indicate whether the user is expected to enter their username or their email address to log in.
  • AllowGenerationOfClaimsWithNullValues: This property will allow the technical profile to generate a claim with a null value. For example, if the UI displays a checkbox but the user does not check the checkbox, the claim will still appear in the claim bag with a null value.
  • ContentDefinitionReferenceId: The content definition to use to render the UI for the self-asserted form.
  • EnforceEmailVerification: When entering an email when signing up or editing their profile, the self-asserted technical profile will attempt to send an email to the user at the email address with a code. The user will need to enter the code. This is to prevent entering invalid email addresses. By default, this property is true, but this validation can be turned off by setting this property to false.
  • setting.retryLimit: Sets a limit on the number of times the user can fail validation on the values that the user enters into the form and that is validated using a validation technical profile. For example, if the user continues to enter a bad or taken username on sign up, the limit will be reached, an error will be shown, and the user journey will terminate.
  • SignUpTarget: The claims exchange technical profile that will be called on sign up to create the user’s account.
  • setting.showCancelButton: Displays the cancel button. The default value is true.
  • setting.showContinueButton: Displays the continue button. The default value is true.
  • setting.showSignupLink: Displays the sign up button. The default value is true. Setting this value to false will prevent the user from being able to sign up using this user journey.
  • setting.forgotPasswordLinkLocation: Displays the forgot password link. AfterLabel is the default and displays the link directly after the label or after the password input field when there is no label. AfterInput displays the link after the password input field. AfterButtons displays the link on the bottom of the form after the buttons. None removed the forgot password link.
  • setting.enableRememberMe: Displays the Keep me signed in checkbox. By default, this is false.
  • setting.inputVerificationDelayTimeInMilliseconds: Sets a delay to improve the user experience by waiting for the user to stop typing before attempting to do a validation of a form field as the user is typing. By default, this is 2000 milliseconds (2 seconds).
  • IncludeClaimResolvingInClaimsHandling: By default this is false. Set it to true if you want to use claim resolvers (we’ll cover those later) in the technical profile.
  • setting.forgotPasswordLinkOverride: Specifies a claims exchange technical profile to execute for the password reset link. This is useful if you want to execute a user journey for resetting a password.

Let’s look at the metadata for the SelfAsserted-LocalAccountSignin-Email technical profile. The SignUpTarget value is set to SignUpWithLogonEmailExchange. The setting.operatingMode value is set to Email to indicate that login occurs with the email address and not the username. The ContentDefinitionReferenceId value is set to api.localaccountsignin.

Let’s start with the content definition:

This content definition will display a form where the user will enter their email address and password to log in.

Let’s go back to the SelfAsserted-LocalAccountSignin-Email technical profile. In the technical profile, we have one input claim: signInName. If the signInName claim has a value, it will be inserted into the text field on the form for the username.

When the user enters their username and password and click the sign in button, SelfAsserted-LocalAccountSignin-Email will execute the login-Noninteractive technical profile to validate the username and password. The login-Noninteractive technical profile is defined as:

login-Noninteractive is an OpenID Connect technical profile and uses the OpenID Connect protocol such as the Azure Active Directory B2C directory. The login-Noninteractive technical profile has the following metadata:

  • ProviderName is set to https://sts.windows.net as the name of the identity provider.
  • METADATA is set to the Open ID Connect metadata endpoint for the Azure AD B2C tenant.
  • authorization_endpoint is set to the URL of the token endpoint for the Azure AD B2C tenant.
  • response_types is set to id_token to retrieve the ID token of the user after being authenticated.
  • response_mode is set to query to indicate that the authentication result should be sent in the query string of the redirect.
  • scope is set to email and openid. The email scope will return the user’s email in the ID token. The openid scope just identifies the user of the OpenID Connect protocol.
  • UsePolicyInRedirectUri is set to false.
  • HttpBinding is set to POST to indicate that POST should be used as the method for invoking the OpenID Connect endpoint.

The login-NonInteractive technical profile sends several claims to the OpenID Connect provider as input claims:

  • signInName: the username entered into the form.
  • password: the password that the user entered into the form.
  • grant_type: password
  • scope: openid
  • nca: The value of the nca claim, which defaults to 1.

The nca claim is new. Here is it’s schema:

If the user is successfully authenticated using the password, the login-NonInteractive technical profile will return several output claims that will be added to or updated in the claim bag:

  • objectId: The unique identifier for the user in the directory.
  • tenantId: The unique identifier of the Azure AD B2C tenant.
  • givenName: The user’s first or given name.
  • surName: The user’s surname, family name, or last name.
  • displayName: The display name for the user.
  • userPrincipalName: The complete username from Active Directory.
  • authenticationSource: Defaults to localAccountAuthentication.

The claim schemas for the new fields are shown below:

So if login-NonInteractive succeeds, then SelfAsserted-LocalAccountSignin-Email also succeeds and the user will be considered authenticated and logged in. SelfAsserted-LocalAccountSignin-Email will output the following claims back to the claim bag:

  • signInName
  • password
  • objectId
  • authenticationSource

If the user does not have a user account and chooses to sign up, then the output claims will not be output, the current step will end, and then the SignUpWithLogonEmailExchange claims exchange will be evaluated in the next step and the LocalAccountSignUpWithLogonEmail technical profile will be executed.

Step 2: ClaimsExchange

Before we dive into LocalAccountSignUpWithLogonEmail, look at the second orchestration step:

It has a <Precondition> element that checks to see if the objectId claim exists. If objectId exists, then the second orchestration step will be skipped and the user journey will advance automatically to the third step. If the user’s signing up, however, we’re going to dive into the LocalAccountSignUpWithLogonEmail technical profile:

LocalAccountSignUpWithLoginEmail is another self-asserted technical profile. It will present a form that the user will be asked to fill out in order to create a local user account. The metadata for the technical profile specifies that the technical profile should use the api.localaccountsignup content definition. (The IpAddressClaimReferenceId metadata item appears to be undocumented.)

This content definition is identical to api.localaccountsignin that we used earlier.

LocalAccountSignUpWithLogonEmail specifies the email claim as an input claim, meaning that, if provided, the email field will be automatically populated with the user’s email address.

After the user enters the required information into the form, the AAD-UserWriteUsingLogonEmail technical profile is called to validate the claims in the form:

AAD-UserWriteUsingLogonEmail inherits configuration from AAD-Common, which is used as a base technical profile for activities that interact with the directory. AAD-UserWriteUsingLogonEmail is an Active Directory provider technical profile that writes the user object to the directory.

The Active Directory technical profile requires exactly one input claim that is used to find an existing account or create a new one. AAD-UserWriteUsingLogonEmail uses the email claim to uniquely identify the user. The email claim is mapped to the signInNames.emailAddress attribute in the directory. This allows email to map to an account that may have multiple email addresses.

The <PersistedClaim> elements inform the Active Directory technical profile which claims to write to the directory for the new user:

  • email: written to signInNames.emailAddress.
  • newPassword: written to password.
  • displayName: defaults to unknown if not provided.
  • passwordPolicies: defaults to DisablePasswordExpiration.
  • givenName
  • surname

AAD-UserWriteUsingLogonEmail will output the following claims:

  • objectId: the unique identifier for the user.
  • newUser: set to true.
  • authenticationSource: set to localAccountAuthentication.
  • userPrincipalName
  • signInNames.emailAddress

If AAD-UserWriteUsingLogonEmail succeeds, then the user account now exists in the directory. LocalAccountSignUpWithLogonEmail can then return successfully and output these claims to the claim bag:

  • objectId
  • email
  • newPassword
  • reenterPassword
  • executed-SelfAsserted-Input: defaults to true
  • authenticationSource
  • newUser
  • displayName
  • givenName
  • surname

The new claims are defined below:

Step 3: ClaimsExchange

By step 3 of the user journey, we either have a logged in user, or a newly created user. Step 3 executes the AAD-UserReadUsingObjectId technical profile to read additional data about the user out of the directory:

AAD-UserReadUsingObjectId is also based off of AAD-Common, which we covered earlier. AAD-UserReadUsingObjectId reads the user object in the directory using the objectId claim that was returned either by authenticating or creating the user.

AAD-UserReadUsingObjectId will read the user object and output these claims to the claim bag:

  • signInNames.emailAddress
  • displayName
  • otherMails
  • givenName
  • surname

otherMails is defined as:

Step 4: SendClaims

Step 4 is the end of our user journey. By this point, the user has logged in or has signed up. We have also read all of the user profile data that we care about from the directory. In step 4, we take all of the claims that we have gathered and issue the JWT token for the user by executing the JwtIssuer technical profile:

The result of this technical profile is that the JWT ID token will be returned to the relying party for the user.

With the conclusion of Step 4, our user journey is complete and the relying party application will receive the ID token for the authenticated user.

Where Have We Gone?

When I set out to write this post, my intent was to cover the whole journey from deconstruction to building a new custom policy. Unfortunately, looking at the length of this post, I think one post is not enough for this topic. Instead, we have covered the first half of the journey by using the Azure Active Directory B2C’s custom policies starter kit to decompose the sign up or sign in relying party policy to understand what it does. Along the way, we have hopefully gained a much better understanding about how custom policies work.

In the next post, I will resume the journey with the second half where I will take the information that I have gathered, design a new sign up or sign in feature, and will implement it from scratch as a custom policy.

--

--

Michael Collins
Neudesic Innovation

Senior Director of Application Innovation at Neudesic; Software developer; confused father