Tips and tricks for working with custom policies in Azure AD B2C

Rory Braybrook
The new control plane
19 min readMay 15, 2019

I’ve been doing a lot of work with custom policies lately and came across a number of things that might help other custom policy developers so I thought it worth while to note them.

This article will be updated as I come across other tips and tricks.

Custom policies

Custom Signin policy

There is a custom SignUpOrSignin policy but no Signin only policy.

You can achieve this by setting SignUp to “False”.

<Item Key=”setting.showSignupLink”>False</Item>

Customise screen UI

These settings turn the feature on / off.

The default is “true” = feature on.

  • setting.showContinueButton
  • setting.showCancelButton
  • EnforceEmailVerification (removes the need to verify the email — useful for testing)
  • Also PartnerClaimType=”Verified.Email” can be deleted
  • setting.forgotPasswordLinkLocation (Displays the forgot password link e.g. None removes the forgot password link)
  • language.button_continue = Done (You can control the text of the buttons e.g. the “Continue” button shows as “Done”)
  • setting.retryLimit controls the number of times a user can try to provide the data that is checked against a validation technical profile. For example, a user tries to sign-up with an account that already exists and keeps trying until the limit reached. This results in the error message:

AADB2C90157: User has exceeded the maximum number for retries for a self-asserted step.

Customise error messages

In the “ContentDefinitions”, you can customise the error message in the “RecoveryUri” section of the flow e.g. “api.signuporsignin”.

<ContentDefinition Id=”api.idpselections”>
<! — <RecoveryUri>~/common/default_page_error.html</RecoveryUri>

However, this does not work if B2C throws an internal exception.

To cover that case, customise this section:

<! — This content definition is to render an error page that displays unhandled errors. →
<ContentDefinition Id=”api.error”>

Add “email” as a claim to the JWT for sign-in

The sign-in flow calls “AAD-UserReadUsingObjectId” that has

<OutputClaim ClaimTypeReferenceId=”signInNames.emailAddress” />

Note that “email” only applies to social logins.

Then in your RP policy, add:

<OutputClaim ClaimTypeReferenceId=”signInNames.emailAddress” PartnerClaimType=”email” />

This will output the claim as type “email” e.g.

“oid”: “9a6…a8f”,
email”: “jbloggs@gmail.com”,
“name”: “Joe Bloggs”,

Localisation

You can alter the message texts by “localising” English to English! i.e.

<ContentDefinition Id=”api.signuporsigninwithkmsi”>
<LoadUri>~/tenant/default/unified.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:1.1.0</DataUri>
<Metadata>
<Item Key=”DisplayName”>Signin and Signup with KMSI</Item>
</Metadata>
<LocalizedResourcesReferences MergeBehavior=”Prepend”>
<LocalizedResourcesReference Language=”en” LocalizedResourcesReferenceId=”api.localaccountsignup.en” />
</LocalizedResourcesReferences >
</ContentDefinition>

</ContentDefinitions>

<Localization Enabled=”true”>
<SupportedLanguages DefaultLanguage=”en” MergeBehavior=”ReplaceAll”>
<SupportedLanguage>en</SupportedLanguage>
</SupportedLanguages>
<LocalizedResources Id=”api.localaccountsignup.en”>
<LocalizedStrings>
<LocalizedString ElementType=”ClaimsProvider” StringId=”SignUpWithLogonEmailExchange”>Email signup</LocalizedString>
<LocalizedString ElementType=”UxElement” StringId=”forgotpassword_link”>Forgot Password?</LocalizedString>
<LocalizedString ElementType=”UxElement” StringId=”createaccount_link”>Register</LocalizedString>
</LocalizedStrings>
</LocalizedResources>
</Localization>

The list of messages you can change is here. More info. here.

Tokens

A neat trick is to set the reply_uri to “https://jwt.ms”.

This enables you to see the JWT sent by the custom policy.

Calling REST API

The B2C mechanism for this only allows the GET and POST method. If you want to use one of the others e.g. PATCH, then use the REST interface to call an Azure web API or Azure function and implement the functionality there.

Extension attributes

The name of the extension attribute changes between accessing it via custom policies and via the GraphAPI.

For the Graph API , the name includes the clientID of the standard b2c-extensions-app. (Defined under “AppRegistrations” in the Azure AD section of the tenant, not the Azure AD B2C section).

{
“extension_199…770_isActive”: “False”
}

The name in the custom policy is:

extension_isActive

More info. here.

The Graph API call to see all the user’s extension attributes is: https://graph.microsoft.com/beta/users/UsersObjectId

It will return the user’s extension attributes in the response.

Reference: Here.

Setting values to attributes

If you want to write a specific value to an attribute e.g. in “AAD-UserWriteProfileUsingObjectId”, use this format.

<PersistedClaim ClaimTypeReferenceId=”some_attribute” DefaultValue=”true” AlwaysUseDefaultValue=”true”/>

This e.g. sets the attribute to “true”.

Valid B2C attributes

There is a list of supported Azure AD B2C user profile attributes.

“It also notes those attributes that are not supported by Microsoft Graph, as well as Microsoft Graph attributes that should not be used with Azure AD B2C”.

Password policies

The basics are described here.

However, this is discussed in more detail using “Predicates” here.

Also, note that if you downgrade the policy e.g. from “Strong” to “Simple” (8 characters minimum — no restrictions on characters), you also need to disable the policy in the Relying Party.

<InputClaims>
<InputClaim ClaimTypeReferenceId=”passwordPolicies” DefaultValue=”DisablePasswordExpiration, DisableStrongPassword”/>

If you want a simple password policy e.g. you don’t care what characters are entered but the length must be between 10 and 20 characters, this will work:

<Restriction>
<Pattern RegularExpression="^.{10,20}$" HelpText="Password must be 10-20 characters in length" />
</Restriction>

Predicates

You can use these for validation e.g. if you have a field for an email address and you want to validate it, you can do:

<Predicates>
<Predicate Id="Email_Regex" Method="MatchesRegex" HelpText="Please enter valid email address.">
<Parameters>
<Parameter Id="RegularExpression">(?:[a-z0-9!#$%'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])</Parameter>
</Parameters>
</Predicate>
</Predicates>
<PredicateValidations>
<PredicateValidation Id="Email_Predicate">
<PredicateGroups>
<PredicateGroup Id="Email_Group">
<UserHelpText>Please enter valid email address.</UserHelpText>
<PredicateReferences>
<PredicateReference Id="Email_Regex"/>
</PredicateReferences>
</PredicateGroup>
</PredicateGroups>
</PredicateValidation>
</PredicateValidations>

and then in your text field:

<ClaimType Id="emailAddress">
<DisplayName>Sign in name</DisplayName>
<DataType>string</DataType>
<UserHelpText/>
<UserInputType>TextBox</UserInputType>
<PredicateValidationReference Id="Email_Predicate"/>
</ClaimType>

There are a number of predicate methods:

  • IsLengthRange
  • MatchesRegex
  • IncludesCharacters
  • IsDateRange

Restrictions

You could do the same as above with restrictions.

<ClaimType Id="signInName">
<DisplayName>Sign in name</DisplayName>
<DataType>string</DataType>
<UserHelpText/>
<UserInputType>TextBox</UserInputType>
<!-- <PredicateValidationReference Id="Email_Predicate"/> -->
<Restriction>
<Pattern RegularExpression="(?:[a-z0-9!#$%'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%'*+\/=?^_`{|}~-]+)*|(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*)@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])" HelpText="Please enter a valid email address."/>
</Restriction>
</ClaimType>

Splitting a string collection into a group of smaller string collections

You can do this with regex, as per this post.

Overwriting user journey

You can “overwrite” part of the user journey in the SUSI RelyingParty policy e.g.

<UserJourneys>
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2AssertionIssuer"/>
</OrchestrationSteps>
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />

This overrides step 7 of the “SignUpOrSignIn” user journey to send a SAML XML token instead of a JWT.

Extending technical policy

You can also extend a technical policy.

e.g. you want to use this extra attribute for one particular journey but don’t want it in the base file, so you add it to an extension file for that use case.

<TechnicalProfile Id="AAD-UserWritePhoneNumberUsingObjectId">
<PersistedClaims>
<!-- Add the MFA preferred method to the persisted claims collection -->
<PersistedClaim ClaimTypeReferenceId="extension_mfaByPhoneOrEmail"/>
</PersistedClaims>
</TechnicalProfile>

Using username instead of email address

You can do this with the built-in policies by configuring the local account IDP.

But note that this is tenant wide.

You can do this with a custom policy that is not tenant wide.

It is defined by the metadata:

<Item Key="LocalAccountType">Username</Item>                             <Item Key="LocalAccountProfile">true</Item>

and:

<Item Key="setting.operatingMode">Username</Item>

Passing username in the JWT

To do this, you need to use custom policies.

This cannot be done with built-in policies.

The name to use in the RP is the “signInNames.username” attribute.

Reference: Here.

SAML parameters

There’s a number of parameters you can set in the metadata e.g. for signing and encryption:

<Item Key="RequestsSigned">false</Item>
<Item Key="ResponsesSigned">false</Item>
<Item Key="WantsEncryptedAssertions">false</Item>
<Item Key="WantsSignedAssertions">false</Item>

Policy naming

The name of the custom policy displayed in B2C is not derived from the name of the xml file.

It is derived from the “PublicPolicyUri” value in the xml file.

PublicPolicyUri=”http://mydomain.onmicrosoft.com/Test-PasswordReset"

The name displayed will be:

“B2C_1A_Test-PasswordReset”.

Escape XML invalid characters

To include XML invalid characters, use their escaped equivalents.

So replace:

< with &lt;

> with &gt;

" with &quot;

' with &apos;

& with &amp;

Null claims

You can set a null value in a claim but can’t check for one.

As per @Chris, you can create a null claim using the “NullClaim” transformation and then compare this to the claim you are checking for null using the “CompareClaims” transformation.

Claim resolvers

You can use resolvers to provide context information about an authorization request, such as the policy name, request correlation ID, user interface language, and more.

e.g.

{OIDC:ClientId} The client_id query string parameter

{Policy:PolicyId} The relying party policy name

Multiple user logins

You can now login with either local accounts (email) or phone numbers.

If you use both, this gives you two different identities. If you want the same person to be able to use both, you can “link” the two.

You can use the Graph API to add the information.

The JSON format for the update would be e.g.

{
"signInNames": [
{
"type": "phoneNumber",
"value": "+64123456"
},
{
"type": "emailAddress",
"value": "joe@company.com"
}
]
}

Mix and match with user flows and custom policies

You cannot sign up with a user flow and then login with a custom policy.

Refer this.

Debugging

The “User Journey Recorder” is pretty much a must.

More details here.

If you have set up “Application Insights”, an easy way to find the error in Analytics is to use the term:

search “exception”

or:

search “An error occurred while processing the request”

KMSI

Keep Me Signed In.

Hows does it work?

“KMSI/No KMSI effects how AAD B2C sets its web session cookie on the client.

KMSI: Sets a persistent session cookies for a period you want. It means the user doesn’t need to re-present credentials to AAD B2C the next time they visit your website, even if they closed the browser. The maximum time you can set this to is around 65yrs.

No KMSI: Sets a session cookie (non persistent). Users have to present their credentials to AAD B2C the next time they visit your website if they closed the browser. If they didn’t close the browser, just the tab, the maximum time that they can login without re-presenting credentials for your website is 24hrs.

KMSI + Implicit flow (SPA) — Above rules apply to the login and the token renewal operations. A hidden iframe is used and relies on the AAD B2C cookie. Uses a hidden iframe which uses the AAD B2C session cookie to issue a new AT.

KMSI + PKCE (SPA) — Above rules ignored for token renewals where the refresh token is valid. Above rules only apply if the Refresh Token expired or doesn’t exist, this is the fallback. Otherwise, they don’t apply since Refresh token flow doesn’t rely on cookies. Max 24hr refresh token. Uses a hidden iframe and the OIDC refresh token flow is processed. But when the AAD B2C session cookie is processed, you will get a new Auth Code.

KMSI + Code/PKCE (Web App) — Above rules ignored for token renewals where the refresh token is valid. Above rules only apply if the Refresh Token expired or doesn’t exist. Otherwise, they don’t apply since Refresh token doesn’t rely on cookies. Max refresh token 90 days after which you fall back to the cookie. But when the AAD B2C session cookie is processed, you will get a new Auth Code.

KMSI doesn’t work for social accounts because AAD B2C always relies on the social accounts session. It would be a security flaw if AAD B2C kept you signed in but your account was deleted at the federated IdP. You can tweak the session management to ignore calling back to a social IdP, but there is no UX to select KMSI for a social IdP nor can it be done programmatically today.

Since KMSI revolves around session cookies, it’s evaluated only when a round trip to AAD B2C is performed. That is when your refresh token expired (Code/PKCE flow) or you want a new access token (Implicit), or you’re doing a fresh logon. These scenarios involve a round trip where the AAD B2C session cookie is processed.

You do not need to configure MSAL in any way to tie with your KMSI config. AAD B2C itself processes cookies when required.

Since you’re using SPA + PKCE, you want to maximise the session lifetime as it is the fallback to the RT flow (max 24hrs), but the session lifetime is also only max 24hrs. So you’re forced to use KMSI to give you up to 65yrs session lifetime, at a cost that this session persists even after the browser was closed. There will be a hidden iframe to obtain a new access token each time it (AT) expires (max 24hrs) using the refresh token or cookie if the RT expired too (max 24hrs).

How is it triggered ?— MSAL library knows the validity of the Access Token and performs the required refresh token calls when needed. AAD B2C validates the RT, and otherwise falls back to the session cookie. So there are no code changes needed to accommodate any of this logic.

Every single call to your API in your SPA should call acquireTokenSilent() with the appropriate scopes prior to the API request itself. MSAL determines if a valid AT exists and only then makes a network request if one does not”.

Reference: Here.

Federation

If you want to add another IDP e.g. ADFS, you still need to use the Social templates e.g. “SocialAndLocalAccounts”.

“Social” includes federation.

It will not work with “LocalAccounts”.

Certificates

B2C will not work with any IDP that has self-signed certificates.

They have to be CA issued ones.

“Let’s Encrypt” is a free option to create the certificate.

JavaScript

If you want your custom policies to use JavaScript for the application branding etc., you must add an element to your custom policy, select a page contract, and use b2clogin.com in your requests.

Content definitions

You can create your own content definitions with arbitrary id’s e.g. “api.registration.error” but they need to inherit a DataUri from the list.

DefaultCpimIssuerTechnicalProfileReferenceId

The error message is something like:

“User journey ‘SignIn’ in policy ‘B2C_1A_Policy’ of tenant ‘my-tenant.onmicrosoft.com’ has 2 sendClaims steps. Please specify a DefaultCpimIssuerTechnicalProfileReferenceId attribute set to the default issuer technical profile reference id”

To get around this, add this to your UserJourney start tag:

DefaultCpimIssuerTechnicalProfileReferenceId=”JwtIssuer”

e.g. the User Journey would look like:

<UserJourney Id=”SignUpSignIn” DefaultCpimIssuerTechnicalProfileReferenceId=”JwtIssuer”>

Thanks to the Advisors for this tip!

Order of execution

Technical profiles (TP) are executed in a set order. This may not match the order of the XML elements in the TP xml.

Notice that input claims are read from the claims bag and output claims are written to the claims bag.

Chaining screens together

To see how to put a number of screens together to make up a user journey, copy a claim from one screen to another and make a claim read-only, refer to my post.

B2C as SAML IDP (SP Initiated)

To see how B2C can act as a SAML IDP using the SP Initiated flow, refer to my post.

B2C as SAML IDP (IDP Initiated)

To see how B2C can act as a SAML IDP using the IDP Initiated flow, refer to my post.

Facebook error

The “SocialAndLocalAccounts” custom policy includes an example for Facebook.

You may get this error when you try and upload:

“… defines an invalid value “” for the metadata item “client_id. …””

The problem here is that you may not want to use Facebook and so have not configured it.

You can provide a dummy ID in the extensions file:

<ClaimsProvider>                             <DisplayName>Facebook</DisplayName>                             <TechnicalProfiles>
<TechnicalProfile Id="Facebook-OAUTH"> <Metadata>
<Item Key="client_id">123456</Item> <Item Key="scope">email public_profile</Item>

Concatenating a static value to a string

You can add a static value by using FormatStringClaim.

Here we have added the country code (+64) to the phone number.

The InputClaim is substituted in the {0} construct.

<ClaimsTransformation Id="AddCountryCodeToPhoneNumber" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="nationalNumber" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="+64{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="nationalNumber" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>

In a similar vein, you can use this construct to concatenate claims using Value="{0} {1}”e.g.

First name + last name = Display name

<ClaimsTransformation Id="Construct_DisplayName" 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>

Issue with “ClaimsEquals”

This is discussed here.

If you are having problems, use a ClaimsTransformation for “CompareClaims”.

Userinfo endpoint

This endpoint is implemented in B2C.

Deleting claims

You can delete a claim (and that includes an extension attribute).

This also works for a list of claims.

<TechnicalProfile Id="AAD-DeleteClaimsUsingObjectId">
<Metadata>
<Item Key="Operation">DeleteClaims</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="extension_claim1" />
<PersistedClaim ClaimTypeReferenceId="extension_claim2" /> </PersistedClaims>
<OutputClaims />
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>

Working with dates and strings

The transform “AssertDateTimeIsGreaterThan” requires input claims to be of type “string”.

Sometimes, you need to pass “currentDateTime” to this transform! However as the “currentDateTime” is of type “DateTime”, it fails!

You can use this trick:

Use the “DateTime” comparison.

Then assert the Boolean “AssertBooleanClaimisEqualToValue”.

This allows you to work with “DateTime” and “Boolean” to perform the comparison and assertion.

Reference: Here.

Check if a claim is set to an empty string

Use a compare claims transform to return a Boolean

Then use a claimsEqual precondition against this Boolean.

Reference: Here.

“This password has expired” error

You get this error when you create a user via the Graph API with a JSON payload and you then try and authenticate.

The reason is that you have set:

"passwordProfile" : {
"forceChangePasswordNextSignIn": true,
"password": "xWwvJ]6NMw+bWH-d"
}

This must always be set to “false”.

“Setting it to true generates a temporary password, which is marked as expired and requires the user to provide a new password.

Since, in Azure AD B2C, there is a different mechanism for resetting password (i.e. by using Password Reset User flows/Custom Policies), users don’t get the option to reset the password and only get “The password has expired” message”.

Reference: Here.

Creating a user with a non-GUID UPN

If you create a user via Graph API with the following json:

{
"accountEnabled": true,
"displayName": "Susan Vance",
"mail": "SusanV@domain.onmicrosoft.com",
"mailNickname": "SusanV",

"identities": [
{
"signInType": "emailAddress",
"issuer": "domain.onmicrosoft.com",
"issuerAssignedId": "SusanV@domain.onmicrosoft.com"
}
],
"passwordProfile" : {
"forceChangePasswordNextSignIn": false,
"password": "xWwvJ]6NMw+bWH-d"
}
}

you will see that the UPN created is of the form:

"userPrincipalName": "6f33bcf8-7b9e-5386-a80c-35a9doaod117@domain.onmicrosoft.com",

If you want a “proper” UPN, you need to add this to the JSON e.g.

{
"accountEnabled": true,
"displayName": "Susan Vance",
"mail": "SusanV@domain.onmicrosoft.com",
"mailNickname": "SusanV",
"userPrincipalName": "SusanV@domain.onmicrosoft.com",
...

Stop user sending claim

Derived from this sample.

“This example policy prevents the user form issuing an access token after resetting the password”.

The user journey contains:

<!-- Demo: this orchestration step blocks the user from issuing the access token, by calling the self asserted page without continue button -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="BlockSendClaims" TechnicalProfileReferenceId="SelfAsserted-PasswordResetUserMessage"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>

Note that you cannot leave out the “SendClaims” step without getting an error.

If we look at the “SelfAsserted-PasswordResetUserMessage” TP, you’ll see that the buttons are disabled and there is no way for the user to continue hence they can’t get the claim.

<!--Demo: this technical profile displays the message to the user-->
<TechnicalProfile Id="SelfAsserted-PasswordResetUserMessage">
<DisplayName>Password reset</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<!--Demo: hide the continue and cancel buttons -->
<Item Key="setting.showContinueButton">false</Item>
<Item Key="setting.showCancelButton">false</Item>

</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GetPasswordResetUserMessage"/>
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userMessage"/>
</InputClaims>
<OutputClaims>
<!--Demo: Show the paragraph claim with the message to the user -->
<OutputClaim ClaimTypeReferenceId="userMessage"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>

B2C html and css templates

You can find sample templates for UI customization here:

git clone https://github.com/Azure-Samples/Azure-AD-B2C-page-templates

This project contains the following templates:

These are the ones referenced e.g. by:

<LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>

Reference: Here.

Subroutines

B2C has the concept of subroutines i.e. a part of a user journey that can be used by many user journeys.

These are called SubJourneys.

“The key component of branching is to allow for better business logic processing in a user journey. Common orchestration steps are grouped into individual pieces to be invoked separately.

A subjourney can simplify a journey where multiple orchestration steps are coupled together (having same preconditions).

A subjourney is called only from a user journey, it shouldn’t call another subjourney.”

More on SubJourneys here.

Single Logout (SLO)

SLO (as the name implies) will logout of all B2C identity providers when you logout of one.

The way it attempts to sign out from any federated identity providers is:

  • OpenId Connect — If the identity provider well-known configuration endpoint specifies an end_session_endpoint location
  • OAuth2 — If the identity provider metadata contains the end_session_endpoint location
  • SAML — If the identity provider metadata contains the SingleLogoutService location

If you don’t want this behaviour, you can disable it:

“You can disable the sign out from federated identity providers, by setting the identity provider technical profile metadata SingleLogoutEnabled to false".

In terms of applications:

“Azure AD B2C sends an HTTP GET request to the registered LogoutUrl of all the applications that the user is currently signed in to.

Applications must respond to this request by clearing any session that identifies the user and returning a 200 response. If you want to support single sign-out in your application, you must implement a LogoutUrl in your application's code.

To support single sign-out, the token issuer technical profiles for both JWT and SAML must specify:

  • The protocol name, such as <Protocol Name="OpenIdConnect" />
  • The reference to the session technical profile, such as UseTechnicalProfileForSessionManagement ReferenceId="SM-OAuth-issuer" />”.

References: Here

Error with extension attributes

You may get an error like this:

Validation failed: 1 validation error(s) found in policy "B2C_1A_FORCEPASSWORDRESET" of tenant "tenant.onmicrosoft.com".The InputClaims mismatched in ClaimsTransformation with id "ComparePasswordResetOnWithCurrentDateTime" with TransformationMethod "DateTimeComparison". The following InputClaims were declared in the Policy but were not expected by the TransformMethod: [DateTime]secondDateTime.

In this case, the format of “DateTimeComparison” was correct but “secondDateTime” was an extension attribute.

This was ignored because I had not added support for extension attributes.

Replacing ApplicationObjectId and ClientId as per the link fixed the problem.

MFA

The PhoneFactor provider enables you to control the type of call.

setting.authenticationMode: (mixed| phone | sms)

  • mixed: allows both the “call” and “text” options
  • phone: allows “call”
  • sms: allows “text”

setting.autodial: (true | false)

If the “setting.authenticationMode” is set to “phone” or “sms” and “setting.autodial” is set to true, then the phone number is dialled or messaged automatically without any user interaction.

<ClaimsProvider>
<DisplayName>PhoneFactor</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<DisplayName>PhoneFactor</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.PhoneFactorProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.authenticationMode">phone</Item>
<Item Key="setting.autodial">true</Item>
</Metadata>
...
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>

To see how to customise the phone drop-down for MFA, refer this.

Utilities

There is a collection of utilities here.

Custom policy manager

This API allows you to upload and manage custom policies programmatically.

Note: This API only accepts user tokens, and not application tokens.

This sample is a Windows Forms interface that allows you to invoke various functions.

  • Select Only show RPs to only show the Relying Party files in the Polices list. You must List Policies for this to update the list based on the selection.
  • Select a Policy and click Delete Policy to delete the policy from the tenant.
  • Select Delete all policies to delete all policies in this tenant.
  • Select Get Access token if you would like to also acquire an access token. This will only work if B2C Resource is not null. Enter the scopes into the B2C Resource text field.
  • To launch a policy, select the Relying Party file from the policy list, and then click Launch with IE or Launch with Chrome. Both options will open an private window.
  • To test a SAML Relying Party, click the SAML SP button. This will use a test site (https://b2csamlrp.azurewebsites.net/SP/) to build a SAML request for your B2C Policy to the authentication endpoint. The b2csamlrp will also parse the resulting SAML Assertion from B2C.

This use the TrustFrameworkPolicy resource type.

Graph API

Write-up here.

GitHub repository here.

This sample is a Windows command-line interface (CLI) that allows you to invoke various methods.

B2C Help gives:

Get-User : Read users from your B2C directory. Optionally accepts an ObjectId as a 2nd argument, and query expression as a 3rd argument.
Create-User : Create a new user in your B2C directory. Requires a path to a .json file which contains required and optional information as a 2nd argument.
Update-User : Update an existing user in your B2C directory. Requires an objectId as a 2nd arguemnt & a path to a .json file as a 3rd argument.
Delete-User : Delete an existing user in your B2C directory. Requires an objectId as a 2nd argument.
Get-Extension-Attribute : Lists all extension attributes in your B2C directory. Requires the b2c-extensions-app objectId as the 2nd argument.
Get-B2C-Application : Get the B2C Extensions Application in your B2C directory, so you can retrieve the objectId and pass it to other commands.
Help : Prints this help menu.
Syntax : Gives syntax information for each command, along with examples.

Note: This sample uses the ADAL library that is being deprecated so there is an MSAL version here.

VS Code extension

This is an extension to VS Code that helps with editing and maintaining custom policies.

  • Autocomplete: With the autocomplete feature, you can save your time customizing a B2C policy. The B2C extension provides you the list of policy settings, claims, technical profiles, and claims transformation aggregated from your policy files.
  • Custom policy explorer: This allows you to click the XML element type and select the element you want to open.
  • Go to definition and find all references.
  • Add elements:

**** B2C Add Identity provider technical profile (Shift+Ctrl+1)

**** B2C Add REST API technical profile (Shift+Ctrl+2)

**** B2C Add Claim Type (Shift+Ctrl+3)

**** B2C Add Application Insights (debug mode) (Shift+Ctrl+4)

  • XML schema help: Move your mouse over any XML tag name, to see the description
  • Policy settings: This allows you to manage the values of your Azure AD B2C environments. When you execute the B2C Policy build command, the VS code extension finds and replaces the values of your settings with the ones configured in the policy file, and creates a directory that contains all of your policy files (after the replacement).

GitHub

There are a few repositories that are useful.

Code samples

There is a collection of code samples that provide links to samples for applications including iOS, Android, .NET, and Node.js.

Specifically, it has samples for:

  • Mobile and desktop apps
  • Web apps and APIs
  • Single page apps
  • SAML test application
  • Azure Function quickstarts
  • Identity verification

Provision B2C tenant

This utility will do the basic provisioning for you.

Note: You have to create the tenant first.

Specifically:

Write-up here.

Community content

The above link also provides the policies and the code samples of the Wingtip Games demo. of Azure AD B2C that is deployed here.

These samples are also available here.

It also provides training materials for learning Azure AD B2C with custom policies.

There is also the Woodgrove Groceries demo. that features both Azure AD B2C and Azure AD B2B for external identities.

This demo. is deployed here.

The corresponding administration application is located here.

The custom policies and the relevant sample source code is available here.

There is a B2C Wiki here.

Have a look at the B2C community. There are some recordings from the training section here. Also GitHub repository here.

Another community sample for custom policies from Chris Padgett is here. This also has a gulp file that will configure the policies with your tenant name and Identity experience ID’s.

Another community sample for custom policies from Yoel Horvitz is here.

Another community sample for custom policies from Jas Suri is here.

There are some advanced custom policies here.

The starter pack has custom policy samples in the scenarios directory.

The Custom CIAM User Journeys custom policy samples are here.

There is a collated collection of all the custom policy samples I could find here.

The B2C code samples for mobile / desktop / SPA / web app. /API / daemon are here.

Training

Solutions

Collection here.

Training

Gaining Expertise with Azure AD B2C course for developers.

Wintellect course.

Note: This is a link to a pdf download.

Videos

Collection here.

Upskilling on B2C

I have a B2C tutorial organised as a list inside Medium.

All good!

--

--

Rory Braybrook
The new control plane

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5