I am struggling a lot with a custom policy for Azure AD B2C. I have based my policies on the jit-migration-v2
files: https://github.com/azure-ad-b2c/user-migration/tree/master/jit-migration-v2
I added 4 new custom attributes and included them in the TrustFrameworkExtension.xml
file: extension_creditorId
, extension_likvidoUserId
, extension_likvidoRole
& extension_title
.
Here is my complete TrustFrameworkExtension.xml
file, where you can see that I added these attributes as output claims and persisted claims:
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" PolicySchemaVersion="0.3.0.0" TenantId="{Settings:Tenant}" PolicyId="B2C_1A_JITMigraion_TrustFrameworkExtensions" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_JITMigraion_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>{Settings:Tenant}</TenantId>
<PolicyId>B2C_1A_JITMigraion_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!--Demo: This claim indicates whether the user need to migrate-->
<ClaimType Id="needToMigrate">
<DisplayName>needToMigrate</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Indicates whether the user need to migrate</AdminHelpText>
<UserHelpText>Indicates whether the user need to migrate</UserHelpText>
</ClaimType>
<ClaimType Id="useInputPassword">
<DisplayName>useInputPassword</DisplayName>
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="extension_creditorId">
<DisplayName>Creditor ID</DisplayName>
<DataType>int</DataType>
<AdminHelpText>The ID of the creditor this user belongs to</AdminHelpText>
<UserHelpText>The ID of the creditor this user belongs to</UserHelpText>
</ClaimType>
<ClaimType Id="extension_likvidoUserId">
<DisplayName>Likvido user ID</DisplayName>
<DataType>string</DataType>
<AdminHelpText>The ID of the user in the Likvido DB</AdminHelpText>
<UserHelpText>The ID of the user in the Likvido DB</UserHelpText>
</ClaimType>
<ClaimType Id="extension_likvidoRole">
<DisplayName>Likvido user role</DisplayName>
<DataType>string</DataType>
<AdminHelpText>The role of the user in Likvido</AdminHelpText>
<UserHelpText>The role of the user in Likvido</UserHelpText>
</ClaimType>
<ClaimType Id="extension_title">
<DisplayName>Likvido user title</DisplayName>
<DataType>string</DataType>
<AdminHelpText>The title of the user</AdminHelpText>
<UserHelpText>The title of the user</UserHelpText>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<!--Insert b2c-extensions-app application ID here, for example: 11111111-1111-1111-1111-111111111111-->
<Item Key="ClientId">{Settings:B2CExtensionsAppClientId}</Item>
<!--Insert b2c-extensions-app application ObjectId here, for example: 22222222-2222-2222-2222-222222222222-->
<Item Key="ApplicationObjectId">{Settings:B2CExtensionsAppObjectId}</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- Local account Sign-Up claims provider -->
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<!-- SIGN-IN -->
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="needToMigrate" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!--Demo: Add user migration validation technical profile before login-NonInteractive -->
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-SignIn" ContinueOnError="false" />
<!--Demo: Run this validation technical profile only if user doesn't need to migrate -->
<ValidationTechnicalProfile ReferenceId="login-NonInteractive">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>needToMigrate</Value>
<Value>local</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<!--Demo: Run this validation technical profile only if user needs to migrate -->
<ValidationTechnicalProfile ReferenceId="AAD-MigrateUserUsingLogonEmail">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>needToMigrate</Value>
<Value>local</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- SIGN-UP -->
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<Metadata>
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<ValidationTechnicalProfiles>
<!--Demo: Add user migration validation technical profile before AAD-UserWriteUsingLogonEmail -->
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-SignUp" ContinueOnError="false" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" ContinueOnError="false" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- PASSWORD RESET first page -->
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<Metadata>
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" ContinueOnError="true" />
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-PasswordReset1" ContinueOnError="false" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- PASSWORD RESET second page -->
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId">
<!--Don't run this validation technical profile if objectId is not exists (migrated acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-PasswordReset2">
<!--Don't run this validation technical profile if objectId is exists (existing acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-MigrateUserUsingLogonEmail">
<!--Don't run this validation technical profile if objectId is exists (existing acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<!--Demo: Checks if user exists in the migration table. If yes, validate the credentials and migrate the account -->
<TechnicalProfile Id="REST-UserMigration-LocalAccount-SignIn">
<DisplayName>Migrate user sign-in flow</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{Settings:LikvidoWebAppApiBaseUrl}/users/azure-ad-b2c/migrate</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">True</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
<InputClaim ClaimTypeReferenceId="password" />
<InputClaim ClaimTypeReferenceId="useInputPassword" DefaultValue="false" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="needToMigrate" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="newPassword" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="firstName" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="lastName" />
<OutputClaim ClaimTypeReferenceId="extension_creditorId" PartnerClaimType="creditorId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoUserId" PartnerClaimType="likvidoUserId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoRole" PartnerClaimType="likvidoRole" />
<OutputClaim ClaimTypeReferenceId="extension_title" PartnerClaimType="title" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<!--Demo: Checks if user exists in the migration table. If yes, raises an error -->
<TechnicalProfile Id="REST-UserMigration-LocalAccount-SignUp">
<DisplayName>Migrate user sign-in flow</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{Settings:LikvidoWebAppApiBaseUrl}/users/azure-ad-b2c/raise-error-if-exists</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">True</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInName" />
</InputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<!--Demo: Checks if user exists in Azure AD B2C or the migration table. If not, raises an error -->
<TechnicalProfile Id="REST-UserMigration-LocalAccount-PasswordReset1">
<DisplayName>Migrate user sign-in flow</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{Settings:LikvidoWebAppApiBaseUrl}/users/azure-ad-b2c/raise-error-if-not-exists</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">True</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInName" />
<InputClaim ClaimTypeReferenceId="objectId" />
</InputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="REST-UserMigration-LocalAccount-PasswordReset2">
<DisplayName>Migrate user sign-in flow</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{Settings:LikvidoWebAppApiBaseUrl}/users/azure-ad-b2c/migrate</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">True</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInName" />
<!-- <InputClaim ClaimTypeReferenceId="password" /> -->
<InputClaim ClaimTypeReferenceId="useInputPassword" DefaultValue="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="needToMigrate" />
<OutputClaim ClaimTypeReferenceId="email" />
<!-- Don't return the new password <OutputClaim ClaimTypeReferenceId="newPassword" /> -->
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- Local account Sign-In claims provider -->
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">We can't seem to find your account</Item>
<Item Key="UserMessageIfInvalidPassword">Your password is incorrect</Item>
<Item Key="UserMessageIfOldPasswordUsed">Looks like you used an old password</Item>
<Item Key="ProviderName">https://sts.windows.net/</Item>
<Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
<Item Key="authorization_endpoint">https://login.microsoftonline.com/{tenant}/oauth2/token</Item>
<Item Key="response_types">id_token</Item>
<Item Key="response_mode">query</Item>
<Item Key="scope">email openid</Item>
<Item Key="grant_type">password</Item>
<!-- Policy Engine Clients -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="client_id">{Settings:ProxyIdentityExperienceFrameworkAppId}</Item>
<Item Key="IdTokenAudience">{Settings:IdentityExperienceFramework}</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
<InputClaim ClaimTypeReferenceId="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{Settings:ProxyIdentityExperienceFrameworkAppId}" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="{Settings:IdentityExperienceFramework}" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_creditorId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoUserId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoRole" />
<OutputClaim ClaimTypeReferenceId="extension_title" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-MigrateUserUsingLogonEmail">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />
<PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration,DisableStrongPassword" AlwaysUseDefaultValue="true"/>
<!-- Optional claims. -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
<PersistedClaim ClaimTypeReferenceId="extension_creditorId" />
<PersistedClaim ClaimTypeReferenceId="extension_likvidoUserId" />
<PersistedClaim ClaimTypeReferenceId="extension_likvidoRole" />
<PersistedClaim ClaimTypeReferenceId="extension_title" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_creditorId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoUserId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoRole" />
<OutputClaim ClaimTypeReferenceId="extension_title" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- Facebook claims provider -->
<ClaimsProvider>
<DisplayName>Facebook</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Facebook-OAUTH">
<Metadata>
<!--Demo action required: Change to your Facebook App Id-->
<Item Key="client_id">TODO</Item>
<Item Key="scope">email public_profile</Item>
<Item Key="ClaimsEndpoint">https://graph.facebook.com/me?fields=id,first_name,last_name,name,email</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<!--<UserJourneys>
</UserJourneys>-->
</TrustFrameworkPolicy>
Uploading this custom profile works fine, but when I also add these claims to the SignUpOrSignIn.xml
file, then I receive validation errors. This is my complete SignUpOrSignIn.xml
file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" PolicySchemaVersion="0.3.0.0" TenantId="{Settings:Tenant}" PolicyId="B2C_1A_JITMigraion_signup_signin" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_JITMigraion_signup_signin">
<BasePolicy>
<TenantId>{Settings:Tenant}</TenantId>
<PolicyId>B2C_1A_JITMigraion_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration, DisableStrongPassword"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="needToMigrate" DefaultValue="false" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="extension_creditorId" PartnerClaimType="creditorId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoUserId" PartnerClaimType="likvidoUserId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoRole" PartnerClaimType="likvidoRole" />
<OutputClaim ClaimTypeReferenceId="extension_title" PartnerClaimType="title" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
I receive these error messages when I try to upload this policy:
Validation failed: 4 validation error(s) found in policy "B2C_1A_JITMIGRAION_SIGNUP_SIGNIN" of tenant "likvidostaging.onmicrosoft.com".
Claim type "extension_creditorId" is the output claim of the relying party's technical profile, but it is not an output claim in any of the steps of user journey "SignUpOrSignIn".
Claim type "extension_likvidoUserId" is the output claim of the relying party's technical profile, but it is not an output claim in any of the steps of user journey "SignUpOrSignIn".
Claim type "extension_likvidoRole" is the output claim of the relying party's technical profile, but it is not an output claim in any of the steps of user journey "SignUpOrSignIn".
Claim type "extension_title" is the output claim of the relying party's technical profile, but it is not an output claim in any of the steps of user journey "SignUpOrSignIn".
I don’t understand what I am doing wrong here, so any help is very much appreciated!
2
Answers
I figured out how to make it work now. Apparently, it is required to also add these output claims to the
SelfAsserted-LocalAccountSignin-Email
technical profile (no idea why).So, my resulting
TrustedFrameworkExtensions.xml
looks like this:https://learn.microsoft.com/en-us/azure/active-directory-b2c/validation-technical-profile