AzureB2C - Client credentials and query parameters in a custom policy - azure-ad-b2c

I am trying to use a client credential (ie client_id & client_secret) to access a custom policy (for Azure AD B2C) to get a custom JWT back. I can do this now (see here: OIDC-Connect technical profile only works under a self-asserted validation reference (but need it not to require a self-asserted profile)) however I can't work out how to pass a query parameter through that is recognized in the custom policy.
This is my custom policy: MyPolicy.xml
<?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="{tenant}.onmicrosoft.com"
PolicyId="B2C_1A_MyPolicy"
PublicPolicyUri="http://{tenant}.onmicrosoft.com/B2C_1A_MyPolicy"
DeploymentMode="Development"
UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights">
<BasePolicy>
<TenantId>{tenant}.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!-- JWT -->
<ClaimType Id="value">
<DataType>string</DataType>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName></DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SetQueryParams">
<DisplayName>Set the BrandId and other query params from the OIDC:ClientId field in the url params</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="value" DefaultValue="{OAUTH-KV:value}" AlwaysUseDefaultValue="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="MyUserJourney">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="SetQueryParamsStep" TechnicalProfileReferenceId="SetQueryParams" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Issue JWT -->
<OrchestrationStep Order="2" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="MyUserJourney" />
<UserJourneyBehaviors>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="623badc9-900b-44e3-bd44-bf00d97d9d93" DeveloperMode="true" ClientEnabled="true" ServerEnabled="true" TelemetryVersion="1.0.0" />
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" AlwaysUseDefaultValue="true" DefaultValue="8186c7b3-adca-4c17-b318-939e9d8170b8" />
<OutputClaim ClaimTypeReferenceId="value" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
And am calling it with this:
(x-www-form-urlencoded)
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/B2C_1A_MyPolicy/oauth2/v2.0/token?grant_type=client_credentials&client_id=&client_secret=<client_secret>&scope=https://{tenant}.onmicrosoft.com/api/.default
Which executes the custom policy as I expect; it returns a JWT and it shows fields that I add in there with hardcoded defaults (such as: DefaultValue="8186c7b3-adca-4c17-b318-939e9d8170b8" in the RelyingParty)
This gets more interesting if I change the RelyingParty to have the DefaultValue="{OAUTH-KV:value}" as shown below:
<RelyingParty>
<DefaultUserJourney ReferenceId="MyUserJourney" />
<UserJourneyBehaviors>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="{Settings:AppInsightsKey}" DeveloperMode="true" ClientEnabled="true" ServerEnabled="true" TelemetryVersion="1.0.0" />
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" AlwaysUseDefaultValue="true" DefaultValue="8186c7b3-adca-4c17-b318-939e9d8170b8" />
<OutputClaim ClaimTypeReferenceId="value" DefaultValue="{OAUTH-KV:value}" AlwaysUseDefaultValue="true" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
Which then returns the following:
{
"error": "invalid_grant",
"error_description": "AADB2C90085: The service has encountered an internal error. Please reauthenticate and try again.\r\nCorrelation ID: 1c7b50fa-87bb-4588-8ec0-90e8ed3554be\r\nTimestamp: 2022-12-18 22:05:45Z\r\n"
}
I just want to give client credentials for authentication and supplement them with inputs that I have; I imagine I must be doing something silly as this seems like it should be fairly straight foward.

Policy looks good in the first half of the question.
Call it with https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/B2C_1A_MyPolicy/oauth2/v2.0/token?grant_type=client_credentials&client_id=&client_secret=<client_secret>&scope=https://{tenant}.onmicrosoft.com/api/.default&value=foo

Related

Issue token for application access from ADB2C

Application 1 creates a url with id_token that contains all the necessary info to validate a claim (as of now simply validate the user with email & signed claim).
a. Create a self-signed certificate (New-SelfSignedCertificate )
b. For the same, used tokenbuilder to create a URL as :
https://tenant.b2clogin.com/tenant.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1A_SIGNIN_WITH_EMAIL&client_id=9994db7f-2dc7-4bac-bf8a-93bf04067890&redirect_uri=https://myredirecturl.com/&nonce=80c307115..&scope=openid&response_type=id_token&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IlZWWWwwbUE0YlFYOXg0LVZVTGpfb0VUWHd0VSIsIng1dCI6I......nNpb25fYWdlbmN5
c. Hosted the application with self-signed certificate (Example :
https://b2cidtokenbuilder.azurewebsites.net/)
d. In policy blade created a token with the self-signed certificate (following steps in here)
e. In TrustFrameworkBase updated issuer_secret for JwtIssuer Technical Profile
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_AABIDToken" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
f. In B2C_1A_SIGNIN_WITH_EMAIL policy added the
<Metadata
<Item Key="METADATA">https://tenant.b2clogin.com/tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_SIGNIN</Item>
<Item Key="issuer">https://b2cidtokenbuilder.azurewebsites.net/</Item>
</Metadata>
g. Once I click the AD B2C url I am expecting that the policy B2C_1A_SIGNIN_WITH_EMAIL will allow me to login to the application with an access_token.
(Based on info here I should be able to login to my application once the accesstoken has been created by my policy.
But in my current application what I see is not access_token is getting created but an id_token is created with all the info and since there is no access-token my application is asking for AD B2C login (prompt).
https://myapplication.com/#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiGVla2FiMmNkZXYuYjJjbG9naW4uY29tLzM4NGIzNTdiLTIzMWUtNGY3MS1iYThhLWUxNzBlZDFiMzA1ZC92Mi4....................kLTE5NDYtNGRkYy04YWViLWY5ZDFiMWVmNWMyMSIsImF1Z
Should I be setting some sessionmanagement TP?
My policy is
<?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="tenant.onmicrosoft.com"
PolicyId="B2C_1A_signin_with_email"
PublicPolicyUri="http://tenant.onmicrosoft.com/B2C_1A_signin_with_email">
<BasePolicy>
<TenantId>tenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!--Sample: Stores the error message for unsolicited request (a request without id_token_hint) and user not found-->
<ClaimType Id="errorMessage">
<DisplayName>Error</DisplayName>
<DataType>string</DataType>
<UserHelpText>Add help text here</UserHelpText>
<UserInputType>Paragraph</UserInputType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<!--Sample: Initiates the errorMessage claims type with the error message-->
<ClaimsTransformation Id="CreateUnsolicitedErrorMessage" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="You cannot sign-in without invitation. Please click the link in the email" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
<!--Sample: Initiates the errorMessage claims type with the error message user not found-->
<ClaimsTransformation Id="CreateUserNotFoundErrorMessage" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="You aren't registered in the system! Please contact the Help Desk on 0800 xxx yyy and ask to be added to the system" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<!--Sample: This technical profile specifies how B2C should validate your token, and what claims you want B2C to extract from the token.
The METADATA value in the TechnicalProfile meta-data is required.
The “IdTokenAudience” and “issuer” arguments are optional (see later section)-->
<ClaimsProvider>
<DisplayName>My ID Token Hint ClaimsProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="IdTokenHint_ExtractClaims">
<DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
<Protocol Name="None" />
<Metadata>
<!--Sample action required: replace with your endpoint location -->
<Item Key="METADATA">https://teanant.b2clogin.com/tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_SIGNIN</Item>
<Item Key="issuer">https://b2cidtokenbuilder.azurewebsites.net/</Item>
</Metadata>
<OutputClaims>
<!--Sample: Read the email cliam from the id_token_hint-->
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<!-- Demo: Show error message-->
<TechnicalProfile Id="SelfAsserted-Error">
<DisplayName>Unsolicited error message</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>
<!-- Sample: Remove the continue button-->
<Item Key="setting.showContinueButton">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="errorMessage"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage"/>
</OutputClaims>
</TechnicalProfile>
<!-- Demo: Show unsolicited error message-->
<TechnicalProfile Id="SelfAsserted-Unsolicited">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUnsolicitedErrorMessage" />
</InputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" />
</TechnicalProfile>
<!-- Demo: Show user not found error message-->
<TechnicalProfile Id="SelfAsserted-UserNotFound">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserNotFoundErrorMessage" />
</InputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-Hint">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<!-- Sample: Change the output email claims-->
<!--<OutputClaim ClaimTypeReferenceId="email" />-->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_userId" />
<OutputClaim ClaimTypeReferenceId="extension_tenantAbbreviation" />
<OutputClaim ClaimTypeReferenceId="extension_dateTimeFormat" />
</OutputClaims>
<!-- <OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
</OutputClaimsTransformations> -->
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignInWithEmail">
<OrchestrationSteps>
<!--Sample: Read the input claims from the id_token_hint-->
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<!-- Sample: Check if user tries to run the policy without invitation -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>email</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Read the user properties from the directory-->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingEmailAddress" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-Hint"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- Sample: Check whether the user not existed in the directory -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedUserNotFound" TechnicalProfileReferenceId="SelfAsserted-UserNotFound" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Issue an access token-->
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignInWithEmail" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<!--Sample: Set the input claims to be read from the id_token_hint-->
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="correlationId" DefaultValue="{Context:CorrelationId}" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_userId" />
<OutputClaim ClaimTypeReferenceId="extension_tenantAbbreviation" />
<OutputClaim ClaimTypeReferenceId="extension_dateTimeFormat" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>

Azure B2C Custom Policy - Custom technical profile doesn't work in SignUp

I'm trying to use custom technical profile for Local Account in SignUpOrSignIn user journey.
I have Created the following technical profile in my customtrustframeworkextensions.xml (base:trustframeworkextensions.xml):
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="CustomLocalAccountSignUpWithLogonEmail">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="extension_XXX" />
<!-- Optional claims, to be collected from the user -->
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-ValidateProfile" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
REST-ValidateProfile looks like the following:
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-ValidateProfile">
<DisplayName>Check yyy and zzz Rest API</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Set the ServiceUrl with your own REST API endpoint -->
<Item Key="ServiceUrl">https://asd</Item>
<Item Key="SendClaimsIn">Body</Item>
<!-- Set AuthenticationType to Basic or ClientCertificate in production environments -->
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<!-- REMOVE the following line in production environments -->
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="Api-key" StorageReferenceId="B2C_1A_key" />
</CryptographicKeys>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="extension_xxx" PartnerClaimType="xxx" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="extension_yyy" PartnerClaimType="yyy" />
<OutputClaim ClaimTypeReferenceId="extension_zzz" PartnerClaimType="zzz" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</ClaimsProvider>
I have modified the OrchestrationStep to use custom technical profile in user journey:
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="CustomLocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
When I run my Custom policy and select SignUp the browser shows error: "The page cannot be displayed because an internal server error has occurred."
There are some more spesific details in Application insights:
Exception Message:Output claim type "objectId" specified in the technical profile with id "CustomLocalAccountSignUpWithLogonEmail" in policy "B2C_1A_DEV_signup_signin" of tenant does not specify a UserInputType or a DefaultValue, and is not retrieved from a ValidationTechnicalProfile either., Exception Type:InvalidDefinitionException
Everything works ok when I use name "LocalAccountSignUpWithLogonEmail" for edited technical profile and the ClaimsExChange Looks like this:
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="CustomLocalAccountSignUpWithLogonEmail" />
But when I change name of the modified technical profile, policy doesn't work anymore. It seems to me that claims exchange doesn't work or something. I don't get why because I can't find any other places that refers to LocalAccountSignUpWithLogonEmail.
I want to use custom technical profile because want want to remove some outputclaims whitout touching the base policies.
The technicalProfile "CustomLocalAccountSignUpWithLogonEmail" has an output claim of the objectID which is common when you write something. The most common patterns is:
Technical profile
-> Validation Technical 1 profile
-> Validation Technical 2 profile
Being, validation technical profile 1 maybe calls your REST to validate the profile, then you call validation technical profile 2 to perform a write operation into the directory. When you write into the directory, it outputs an objectID which will output it into your technical profile and that error will go away.

Validation errors when trying to include custom claims in access token

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!
The scope of the output claims of a validation technical profile is limited to the self-asserted technical profile that invokes the validation technical profile, and its validation technical profiles. If you want to use the output claims in the next orchestration step, add the output claims to the self-asserted technical profile that invokes the validation technical profile.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/validation-technical-profile
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:
<?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" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_creditorId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoUserId" />
<OutputClaim ClaimTypeReferenceId="extension_likvidoRole" />
<OutputClaim ClaimTypeReferenceId="extension_title" />
</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>

Validation error uploading Azure AD B2C policy

I created an Azure AD B2C tenant with custom policies last year. Now I am trying to upload the same policies (with IDs changed as necessary) to a new tenant that we have just created and I get the following error when uploading the reset-password policy:
Validation failed: 1 validation error(s) found in policy "B2C_1A_PASSWORDRESET" of tenant "xxx.onmicrosoft.com".Persisted claims for technical profile "AAD-FlipMigratedFlag" in policy "B2C_1A_PasswordReset" of tenant "xxx.onmicrosoft.com" must have one of the following claims: userPrincipalName
These policies implement the Seamless Migration approach to user migration, based on samples in the following repositories:
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack
https://github.com/azure-ad-b2c/samples
https://github.com/azure-ad-b2c/user-migration
As suggested by the error message, I have tried adding userPrincipalName to the PersistedClaims for the AAD-FlipMigratedFlag technical profile, but I get the same error when uploading the policy.
I have also tried re-uploading the existing, working reset-password policy to the existing, working tenant, and I get the same error. Note that in this case I am re-uploading the exact same policy that has already been successfully uploaded and has been working for a year.
So the question is: what has changed and what do I need to do to fix this error?
Here are the relevant parts of my custom policy files. If there are any other parts you need to see, just let me know and I'll add them.
PasswordReset.xml
<?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="xxx.onmicrosoft.com"
PolicyId="B2C_1A_PasswordReset"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_PasswordReset">
<BasePolicy>
<TenantId>xxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="PasswordReset" />
<UserJourneyBehaviors>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="emails" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
TrustFrameworkExtensions.xml
<?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="xxx.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>xxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!-- Holds the value of the migration status on the Azure AD B2C account -->
<ClaimType Id="extension_IsMigrationRequired">
<DisplayName>extension_IsMigrationRequired</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>extension_IsMigrationRequired</AdminHelpText>
<UserHelpText>extension_IsMigrationRequired</UserHelpText>
</ClaimType>
<!-- Holds the value of whether the authentication succeeded at the legacy IdP -->
<ClaimType Id="tokenSuccess">
<DisplayName>tokenSuccess</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>tokenSuccess</AdminHelpText>
<UserHelpText>tokenSuccess</UserHelpText>
</ClaimType>
<!-- Holds the value 'false' when the legacy IdP authentication succeeded -->
<ClaimType Id="migrationRequired">
<DisplayName>migrationRequired</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>migrationRequired</AdminHelpText>
<UserHelpText>migrationRequired</UserHelpText>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Write Password</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Get-requiresMigration-status-password-reset" ContinueOnError="false" />
<ValidationTechnicalProfile ReferenceId="AAD-FlipMigratedFlag" ContinueOnError="false">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>extension_IsMigrationRequired</Value>
<Value>False</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Read migration flag</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Get-requiresMigration-status-password-reset">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_IsMigrationRequired" DefaultValue="false" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Flip migration flag</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-FlipMigratedFlag">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="migrationRequired" PartnerClaimType="extension_IsMigrationRequired" DefaultValue="false" AlwaysUseDefaultValue="true"/>
<!-- NOTE: I added this but still get the error -->
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<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">xxx</Item>
<!--Insert b2c-extensions-app application ObjectId here, for example: 22222222-2222-2222-2222-222222222222-->
<Item Key="ApplicationObjectId">xxx</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
</TrustFrameworkPolicy>
TrustFrameworkBase.xml
<?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="xxx.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkBase"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_TrustFrameworkBase">
<ClaimsProviders>
<ClaimsProvider>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<DisplayName>Reset password using email address</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWritePasswordUsingObjectId">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-Common">
<DisplayName>Azure Active Directory</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<!-- We need this here to suppress the SelfAsserted provider from invoking SSO on validation profiles. -->
<IncludeInSso>false</IncludeInSso>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="PasswordReset">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
I was hoping someone from MS would chime in on this. Since that hasn't happened, I'll go ahead and post my solution.
MS has a bad habit of creating schema where the order of the nodes matter. That was part of the problem here. In addition, the displayName claim was also added to the required list.
So after some trial-and-error, this version of the AAD-FlipMigratedFlag claims provider ended up being the solution:
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Flip migration flag</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-FlipMigratedFlag">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="displayName" />
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
<PersistedClaim ClaimTypeReferenceId="migrationRequired" PartnerClaimType="extension_IsMigrationRequired" DefaultValue="false" AlwaysUseDefaultValue="true"/>
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Have yout tried to add the userPrincipalName claim to the OutputClaims section of PasswordReset.xml?

Azure B2C - The provided token does not contain a valid issuer

I'm using Azure B2C to connect to an external OpenID Connect identity provider, i created a basic user flow within B2C which works but only brings back a small number of claims so i need to create a custom policy to pass custom input parameters to my IDP and collect additional claims.
I started with the SocialAndLocalAccount sample and modified with my IDP's details:
TrustFrameworkBase.xml (Cut down to Technical Profile + User Journey)
...
<ClaimType Id="sub">
<DisplayName>Subject</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="sub" />
</DefaultPartnerClaimTypes>
<UserHelpText />
</ClaimType>
<ClaimType Id="ui_locales">
<DisplayName>UI Locales</DisplayName>
<DataType>string</DataType>
<UserHelpText>Special parameter passed for account authentication to Tell Us Once.</UserHelpText>
</ClaimType>
<ClaimType Id="givenName">
<DisplayName>Given 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>Surname</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="DateOfBirth">
<DisplayName>Date Of Birth</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="birthdate" />
<Protocol Name="OpenIdConnect" PartnerClaimType="birthdate" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your date of birth.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
...
<ClaimsProvider>
<Domain>tellusonce</Domain>
<DisplayName>Tell Us Once</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TUO-OpenIdConnect">
<DisplayName>Tell Us Once Login</DisplayName>
<Description>Login through Tell Us Once</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">this is where i put my client id</Item>
<Item Key="ProviderName">Tell Us Once</Item>
<Item Key="METADATA">this is where my metadata is</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
<InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
<InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
...
<UserJourneys>
<UserJourney Id="SignUpOrSignInOidc">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Create the user in the directory if one does not already exist. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
TrustFrameworkExtensions.xml
<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="itpauthdev.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions"
TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
<BasePolicy>
<TenantId>itpauthdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks></BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<Domain>tellusonce</Domain>
<DisplayName>Tell Us Once</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TUO-OpenIdConnect">
<DisplayName>Tell Us Once Login</DisplayName>
<Description>Login through Tell Us Once</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">this is where i put my client id</Item>
<Item Key="ProviderName">Tell Us Once</Item>
<Item Key="METADATA">this is where my metadata is</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="acr_values" DefaultValue="urn:id.gov.au:tdif:acr:ip1:cl1" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
<InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
<InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInOidc">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Create the user in the directory if one does not already exist. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
signin_signup_oidc.xml
<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="itpauthdev.onmicrosoft.com"
PolicyId="B2C_1A_signup_signin_oidc"
PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_signup_signin"
TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
<BasePolicy>
<TenantId>itpauthdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInOidc" />
<UserJourneyBehaviors></UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" DefaultValue="ClaimNotFound" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
When i click "B2C_1A_signin_signup_oidc" and choose Run User Flow i get my IDP's login screen, once i've successfully logged in i get an authorization code response from my IDP, my IDP has been configured with the "/authresp" redirect uri as per Microsoft documentation ("Redirect Uri" section at the bottom) but then it doesn't seem like "/authresp" is exchanging the code for an id_token because when it redirects to jwt.ms i get "token does not contain a valid issuer".
Fiddler Code Response
By using fiddler's inspector i can see that the IDP's endpoint is responding with a code that looks something like this (not actual code):
<HTML>
<HEAD>
<TITLE>OIDC Form_Post Response</TITLE>
</HEAD>
<BODY Onload="document.forms[0].submit()">
<FORM METHOD="POST" ACTION="https://itpauthdev.b2clogin.com/itpauthdev.onmicrosoft.com/oauth2/authresp"> <INPUT
TYPE="HIDDEN" NAME="code"
VALUE="4cfed1f1-bfcc-42db-b39c-d79480f6d333.056c6e05-eaec-426a-a5e7-bf9f66f962be.15ac212d-38b7-4e9b-80e2-a948be9360e5" />
<INPUT TYPE="HIDDEN" NAME="state"
VALUE="StateProperties=eyJTSUQiOiJ4LW1zLWNwaW0tcmM6M2RkYmRhYjktY2VkZS00MDA4LTliNWYtOGRmZjU2ZDZmZDYzIiveWIlEIjoiMmFtkoc5YjUtMzg0Zi00M2JmLThkMDUtMjIzMGYxNzU1M2JjIiwiVE9CRJI6ImFlMjIwMWViLWU0ZTktNDRlNy04YzczLWI1MmUzN2JhMDFmOCJ9" />
<INPUT TYPE="HIDDEN" NAME="session_state" VALUE="65e01676-90d1-4c5e-a7f4-66dfd7b3211b" /> <NOSCRIPT>
<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>
<INPUT name="continue" TYPE="SUBMIT" VALUE="CONTINUE" />
</NOSCRIPT> </FORM>
</BODY>
</HTML>
Fiddler AuthResp Response
Using Fiddler once again, i can see that AuthResp does not exchange the code for an id_token using the IDP's token endpoint (or encounters an error, i don't get told anything helpful). Instead i get back:
<html>
<head>
<title>Object moved</title>
</head>
<body>
<h2>Object moved to here.
</h2>
</body>
</html>
Questions
I read in documentation somewhere that an "OpenIDConnect" technical profile will automatically exchange the code for an id token but i can't seem to find the document, is my configuration missing a step or do i have to do this manually with an extra userjourney?
If there is miscommunication between "/authresp" and the token endpoint, is there somewhere i can look to get a more descriptive error message? Both jwt.ms and the audit logs just say "Token does not contain a valid issuer".
The error means the id token from the OIDC provider has an issuer (iss) claim that doesn’t match the issuer in the well known OIDC config endpoint. You can add an issuer element to the metadata of TUO-OpenIdConnect technical profile to override it with the value that appears in the token.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile#metadata
issuer : The unique identifier of an OpenID Connect identity provider. The value of issuer metadata takes precedence over the issuer specified in the OpenID well-known configuration endpoint. If specified, Azure AD B2C checks whether the iss claim in a token returned by the identity provider is equal to the one specified in the issuer metadata.
I think the technical profile for sign in is not configured correctly. You can find a working example using OpenIdConnect in the starter pack samples.
Please refer in the samples e.g. to
LocalAccounts\TrustFrameworkBase.xml: Technical profile login-NonInteractive
LocalAccounts\TrustFrameworkExtensions.xml: Technical profile login-NonInteractive
LocalAccounts\SignUpOrSigning.xml
I was using ADB2C and added AAD as identity provider for Home realm discovery. HRD was working as expected and it was redirecting user to AAD(https://login.microsoftonline.com) basis on domain for authentication and after successful authentication it was redirecting to jwt.ms with "AADB2C90238: The provided token does not contain a valid issuer. Please provide another token and try again." error code.
I was able to resolve this by removing below item from Metadata tag.
<Item Key="ProviderName">https://sts.windows.net/ffc9d30c-a7e7-486d-bbcd-66d88af6f4c9/</Item>
Click here for more information

Resources