We have built a service that calls cosmos DB for custom claims to be sent in ID token. The output of the REST API is as per the expected token format below
{"personalAttributes":{"guid":"1b92e96e28b14737acac11d23dcdd3d0","familyName":"ABC","givenName":"PQR","name":"PQR","preferredUserName":"PQR","upn":"PQR#XYZ.com",
"physicalLocation":"London, UK"
"roles":"Engineer","Engineer","Engineer",
"scopes":{"scopeName":"Application 2 - Pipe Spool install","initiative":"nan","operation":"nan","WBS":"nan"},
{"roleName":"Engineer","jobPosition":"Engineer","rolePermissions":"read, report, archive"},
{"roleName":"Engineer","jobPosition":"Engineer","rolePermissions":"read, report, archive"},
{"roleName":"Engineer","jobPosition":"Engineer","rolePermissions":"read, report, archive"}}
However, when B2C includes this in the ID Token it is appending / and . We tried formatting the output which is resulting in B2C not even generating the ID token. PLease advise how we can overcome this
TIA
It looks like you are trying to ask B2C to output a complex object as a claim value. This isn't supported so B2C casting the object to a string - resulting the escaped JSON data you are seeing.
Edit for additional clarity: The personalAttributes value returned from CosmosDB is not a StringCollection (array) as you have defined in the ClaimsSchema. It is a JSON object. B2C does not support object claim types - you'll need to extract the exact values you want from the object.
<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="xxxx.onmicrosoft.com" PolicyId="B2C_1A_oboTrustFrameworkExtensions" PublicPolicyUri="http://xxxx.onmicrosoft.com/B2C_1A_oboTrustFrameworkExtensions" TenantObjectId="3dc9a24f-ed39-4635-b182-e35aadde9cb2">
<BasePolicy>
<TenantId>xxxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_oboTrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="clientId">
<DisplayName>OIDC client id</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="personalAttributes">
<DisplayName>Custom claim</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
<ClaimType Id="roles">
<DisplayName>Custom claim</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
<ClaimType Id="scopes">
<DisplayName>Custom claim</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
<ClaimType Id="tokenOwner">
<DisplayName>aud claim in OBO token</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="jsonSourceClaim">
<DisplayName>JSON output from Custom Service</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="GetClaimsFromJson">
<DisplayName>JSON output from Custom Service</DisplayName>
<DataType>string</DataType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="AssertClientIdAndAudAreEqual" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="tokenOwner" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase" />
</InputParameters>
</ClaimsTransformation>
<ClaimsTransformation Id="GetClaimsFromJsonTransformation" TransformationMethod="GetClaimsFromJsonArray">
<InputClaims>
<InputClaim ClaimTypeReferenceId="jsonSourceClaim" TransformationClaimType="jsonSource" />
</InputClaims>
<InputParameters>
<InputParameter Id="errorOnMissingClaims" DataType="boolean" Value="false" />
<InputParameter Id="includeEmptyClaims" DataType="boolean" Value="false" />
<InputParameter Id="jsonSourceKeyName" DataType="string" Value="key" />
<InputParameter Id="jsonSourceValueName" DataType="string" Value="value" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="personalAttributes" />
<OutputClaim ClaimTypeReferenceId="roles" />
<OutputClaim ClaimTypeReferenceId="scopes" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<!-- Used for custom extension attribute storage -->
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<Item Key="ApplicationObjectId">xxxxxx</Item>
<Item Key="ClientId">xxxxxx</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<Domain>bechtel</Domain>
<DisplayName>Bechtel AAD</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Bechtel-AAD">
<DisplayName>Work or school account</DisplayName>
<Description>Login with your work or school account</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/xxxx/v2.0/.well-known/openid-configuration</Item>
<!-- Update the Client ID below to the Application ID -->
<Item Key="client_id">xxxxxx</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid profile</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<!-- The key below allows you to specify each of the Azure AD tenants that can be used to sign in. Update the GUIDs below for each tenant. -->
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/22d635a3-3930-4779-a82d-155e2d13b75e</Item>
<!-- The commented key below specifies that users from any tenant can sign-in. Uncomment if you would like anyone with an Azure AD account to be able to sign in. -->
<!--<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item> -->
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_BECHTELSECRET" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" 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="email" PartnerClaimType="preferred_username" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" AlwaysUseDefaultValue="true" />
<!--OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" /-->
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="bechtel" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Facebook</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Facebook-OAUTH">
<Metadata>
<Item Key="client_id">facebook_clientid</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>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">db6c8fe9-368f-48a0-af63-19ad47d91e90</Item>
<Item Key="IdTokenAudience">5169b8e9-5bcd-4b04-b5f1-d3ca801bbbef</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="db6c8fe9-368f-48a0-af63-19ad47d91e90" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="5169b8e9-5bcd-4b04-b5f1-d3ca801bbbef" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-GetCustomClaim">
<DisplayName>Get a custom claim</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!--<Item Key="ServiceUrl">https://beciamobo.azurewebsites.net/CustomClaims</Item> -->
<Item Key="ServiceUrl">https://xxxxx.azurewebsites.net/xxxxx</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<!-- Set AuthenticationType to Basic or ClientCertificate in production environments -->
<Item Key="AuthenticationType">None</Item>
<!-- REMOVE the following line in production environments -->
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="{OIDC:Scope}" AlwaysUseDefaultValue="true" />
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{OIDC:ClientId}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="personalAttributes" />
<OutputClaim ClaimTypeReferenceId="roles" />
<OutputClaim ClaimTypeReferenceId="scopes" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetClaimsFromJsonTransformation" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="ValidateOBORequest">
<DisplayName>User ID signup</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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="tokenOwner" />
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{OIDC:ClientId}" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="tokenOwner" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertClientIdAndAudAreEqual" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SUSI">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="AADExchange" />
<!--ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" /-->
</ClaimsProviderSelections>
<!--ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges-->
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<!--Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions-->
<ClaimsExchanges>
<ClaimsExchange Id="AADExchange" TechnicalProfileReferenceId="Bechtel-AAD" />
<!--ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" /-->
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<!--Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions-->
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="5" 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="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetCustomClaim" TechnicalProfileReferenceId="REST-GetCustomClaim" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
<UserJourney Id="OBO">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="Validate" TechnicalProfileReferenceId="ValidateOBORequest" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetCustomClaim" TechnicalProfileReferenceId="REST-GetCustomClaim" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
Please see policy below
<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="xxxx.onmicrosoft.com" PolicyId="B2C_1A_oboTrustFrameworkExtensions" PublicPolicyUri="http://xxxx.onmicrosoft.com/B2C_1A_oboTrustFrameworkExtensions" TenantObjectId="3dc9a24f-ed39-4635-b182-e35aadde9cb2">
<BasePolicy>
<TenantId>xxxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_oboTrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="clientId">
<DisplayName>OIDC client id</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="personalAttributes">
<DisplayName>Custom claim</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
<ClaimType Id="roles">
<DisplayName>Custom claim</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
<ClaimType Id="scopes">
<DisplayName>Custom claim</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
<ClaimType Id="tokenOwner">
<DisplayName>aud claim in OBO token</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="jsonSourceClaim">
<DisplayName>JSON output from Custom Service</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="GetClaimsFromJson">
<DisplayName>JSON output from Custom Service</DisplayName>
<DataType>string</DataType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="AssertClientIdAndAudAreEqual" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="tokenOwner" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase" />
</InputParameters>
</ClaimsTransformation>
<ClaimsTransformation Id="GetClaimsFromJsonTransformation" TransformationMethod="GetClaimsFromJsonArray">
<InputClaims>
<InputClaim ClaimTypeReferenceId="jsonSourceClaim" TransformationClaimType="jsonSource" />
</InputClaims>
<InputParameters>
<InputParameter Id="errorOnMissingClaims" DataType="boolean" Value="false" />
<InputParameter Id="includeEmptyClaims" DataType="boolean" Value="false" />
<InputParameter Id="jsonSourceKeyName" DataType="string" Value="key" />
<InputParameter Id="jsonSourceValueName" DataType="string" Value="value" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="personalAttributes" />
<OutputClaim ClaimTypeReferenceId="roles" />
<OutputClaim ClaimTypeReferenceId="scopes" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<!-- Used for custom extension attribute storage -->
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<Item Key="ApplicationObjectId">xxxxxx</Item>
<Item Key="ClientId">xxxxxx</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<Domain>bechtel</Domain>
<DisplayName>Bechtel AAD</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Bechtel-AAD">
<DisplayName>Work or school account</DisplayName>
<Description>Login with your work or school account</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/xxxx/v2.0/.well-known/openid-configuration</Item>
<!-- Update the Client ID below to the Application ID -->
<Item Key="client_id">xxxxxx</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid profile</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<!-- The key below allows you to specify each of the Azure AD tenants that can be used to sign in. Update the GUIDs below for each tenant. -->
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/22d635a3-3930-4779-a82d-155e2d13b75e</Item>
<!-- The commented key below specifies that users from any tenant can sign-in. Uncomment if you would like anyone with an Azure AD account to be able to sign in. -->
<!--<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item> -->
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_BECHTELSECRET" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" 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="email" PartnerClaimType="preferred_username" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" AlwaysUseDefaultValue="true" />
<!--OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" /-->
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="bechtel" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Facebook</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Facebook-OAUTH">
<Metadata>
<Item Key="client_id">facebook_clientid</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>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">db6c8fe9-368f-48a0-af63-19ad47d91e90</Item>
<Item Key="IdTokenAudience">5169b8e9-5bcd-4b04-b5f1-d3ca801bbbef</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="db6c8fe9-368f-48a0-af63-19ad47d91e90" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="5169b8e9-5bcd-4b04-b5f1-d3ca801bbbef" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-GetCustomClaim">
<DisplayName>Get a custom claim</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!--<Item Key="ServiceUrl">https://beciamobo.azurewebsites.net/CustomClaims</Item> -->
<Item Key="ServiceUrl">https://xxxxx.azurewebsites.net/xxxxx</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<!-- Set AuthenticationType to Basic or ClientCertificate in production environments -->
<Item Key="AuthenticationType">None</Item>
<!-- REMOVE the following line in production environments -->
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="{OIDC:Scope}" AlwaysUseDefaultValue="true" />
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{OIDC:ClientId}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="personalAttributes" />
<OutputClaim ClaimTypeReferenceId="roles" />
<OutputClaim ClaimTypeReferenceId="scopes" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetClaimsFromJsonTransformation" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="ValidateOBORequest">
<DisplayName>User ID signup</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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="tokenOwner" />
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{OIDC:ClientId}" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="tokenOwner" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertClientIdAndAudAreEqual" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SUSI">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="AADExchange" />
<!--ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" /-->
</ClaimsProviderSelections>
<!--ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges-->
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<!--Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions-->
<ClaimsExchanges>
<ClaimsExchange Id="AADExchange" TechnicalProfileReferenceId="Bechtel-AAD" />
<!--ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" /-->
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<!--Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions-->
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="5" 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="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetCustomClaim" TechnicalProfileReferenceId="REST-GetCustomClaim" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
<UserJourney Id="OBO">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="Validate" TechnicalProfileReferenceId="ValidateOBORequest" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetCustomClaim" TechnicalProfileReferenceId="REST-GetCustomClaim" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
You need to apply a GetClaimFromJson transformation to extract claims from JSON.
Related
I am using Azure AD B2C with Custom Policies to implement a sign-up flow where user has to verify the email before proceeds to sign up page.
I found an example here https://github.com/azure-ad-b2c/samples/blob/master/policies/split-email-verification-and-signup/policy/SignUpOrSignIn_SplitEmailVerificationAndSignUp.xml#L61-L77 and I try to modify this code and add a DisplayControl and from there I implement the custom email verification flow.
The emailVerificationControl is also based on the other sample code found here https://github.com/azure-ad-b2c/samples/blob/master/policies/custom-email-verifcation-displaycontrol/policy/SendGrid/DisplayControl_TrustFrameworkExtensions.xml#L142-L163
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="readonlyEmail">
<DisplayName>E-mail Address</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="isForgotPassword">
<DisplayName>isForgotPassword</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Whether the user has clicked Forgot Password</AdminHelpText>
</ClaimType>
<ClaimType Id="termsOfUseConsentChoice">
<DisplayName></DisplayName>
<DataType>string</DataType>
<UserInputType>CheckboxMultiSelect</UserInputType>
<Restriction>
<Enumeration Text=" I agree to the Terms Of Service" Value="AgreeToTermsOfUseConsentYes" SelectByDefault="false" />
</Restriction>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="CreateReadonlyEmailClaim" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readonlyEmail" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Email Verification</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="EmailVerification">
<DisplayName>Initiate Email Address Verification For Local Account</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.localaccountsignup</Item>
<Item Key="language.button_continue">Continue</Item>
</Metadata>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithReadOnlyEmail">
<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>
<Item Key="language.button_continue">Create</Item>
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateReadonlyEmailClaim" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlyEmail" />
<InputClaim ClaimTypeReferenceId="termsOfUseConsentChoice" DefaultValue="AgreeToTermsOfUseConsentNo" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="readOnlyEmail" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="termsOfUseConsentChoice" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<!-- Sample: Disable session management for sign-up page -->
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="ForgotPassword">
<DisplayName>Forgot your password?</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isForgotPassword" DefaultValue="true" AlwaysUseDefaultValue="true" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<Metadata>
<Item Key="setting.forgotPasswordLinkOverride">ForgotPasswordExchange</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignIn_Custom">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="EmailVerification" />
<ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isForgotPassword</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSignUpWithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isForgotPassword</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="PasswordReset" />
</JourneyList>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
</UserJourneys>
<SubJourneys>
<SubJourney Id="PasswordReset" Type="Call">
<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>
</OrchestrationSteps>
</SubJourney>
</SubJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn_Custom" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
Everything seems working fine so far. I can enter my email address and it will send the code using via SendGrid, I can click verify code button and it successfully verified and has a continue button.
1. The DisplayControl
2. Send code to email address
3. Verify email address and continue
But after I click the continue button the page just crash and says, "The page cannot be displayed because an internal server error has occured."
4. Sign up page does not show up, it crashed
The only changes I made is the DisplayControl and it works fine without it (which in this case it uses the Microsoft's default email verification service) so I suppose there is something I did wrong...I would like to get some help if anyone has done anything similar with Azure AD B2C before.
Thanks a lot in advance for your help!
Solved in this github issue: https://github.com/azure-ad-b2c/samples/issues/193
If you are using the following samples Split Email Verification and Custom Email Verification.
The only changes required from these sample policies is that you change the Output Claim from
<TechnicalProfile Id="EmailVerification">
<DisplayName>Initiate Email Address Verification For Local Account</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.localaccountsignup</Item>
<Item Key="language.button_continue">Continue</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<!----------- CHANGE THIS LINE ------------------------------->
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="EmailVerification">
<DisplayName>Initiate Email Address Verification For Local Account</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.localaccountsignup</Item>
<Item Key="language.button_continue">Continue</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<!----------- TO THIS LINE ------------------------------->
<OutputClaim ClaimTypeReferenceId="email" Required="true" />
</OutputClaims>
</TechnicalProfile>
I also noticed this must be added to the following technical profiles: [EmailVerification, LocalAccountDiscoveryUsingEmailAddress]
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
The UserJourney should look like
EmailVerification
LocalAccountSignUpWithReadOnlyEmail
AAD-UserReadUsingObjectId
JwtIssuer
PS: It's also important to note that your ContentDefinitions must be version greater than 2.0.0.
We have custom policies for Azure B2C with MFA enabled.
After successful login, when trying to acquire access token using acquireTokenSilent(), getting the below errors
Refused to display 'https://accounts.google.com/signin/oauth?client_id=xxx&redirect_uri=xxxxx/oauth2/authresp&response_type=code&scope=email+profile&login_hintXXXXXXXXXXX' in a frame because it set 'X-Frame-Options' to 'deny'.
Followed by ClientAuthError: Token renewal operation failed due to timeout.
or
AADB2C90077: User does not have an existing session and request prompt parameter has a value of 'None'
I am getting these errors after every login. If I try to call acquireTokenPopup(), the popup is showing the MFA flow again and asking to authenticate the phone number which is not desired.
I have read many posts regarding these but unable to fix the issue.
Google technical profile:
<TechnicalProfile Id="Google-OAUTH">
<DisplayName>Google</DisplayName>
<Protocol Name="OAuth2" />
<Metadata>
<Item Key="ProviderName">google</Item>
<Item Key="authorization_endpoint">https://accounts.google.com/o/oauth2/auth</Item>
<Item Key="AccessTokenEndpoint">https://accounts.google.com/o/oauth2/token</Item>
<Item Key="ClaimsEndpoint">https://www.googleapis.com/oauth2/v1/userinfo</Item>
<Item Key="scope">email profile</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">0</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<Item Key="client_id">XXXXXXXXXXXXXXXX</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_GoogleSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="loginHint" PartnerClaimType="login_hint" DefaultValue="{OIDC:LoginHint}" />
<InputClaim ClaimTypeReferenceId="prompt" PartnerClaimType="prompt" DefaultValue="{OAUTH-KV:account_prompt}" AlwaysUseDefaultValue="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="id" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="google.com" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
<OutputClaim ClaimTypeReferenceId="requiresMFA" DefaultValue="true"/>
<OutputClaim ClaimTypeReferenceId="isAccessFlow" DefaultValue="{OAUTH-KV:access_flow}" AlwaysUseDefaultValue="true"/>
<OutputClaim ClaimTypeReferenceId="prompt" DefaultValue="{OAUTH-KV:account_prompt}" AlwaysUseDefaultValue="true"/>
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" />
</TechnicalProfile>
Phone Factor Technical profile:
<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<DisplayName>PhoneFactor</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.PhoneFactorProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.phonefactor1.1</Item>
<Item Key="ManualPhoneNumberEntryAllowed">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserIdForMFA" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="Verified.OfficePhone" />
<OutputClaim ClaimTypeReferenceId="newPhoneNumberEntered" PartnerClaimType="newPhoneNumberEntered" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
</TechnicalProfile>
MFA related orchestration steps:
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isActiveMFASession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>requiresMFA</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isAccessFlow</Value>
<Value>true</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>newPhoneNumberEntered</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
requiresMFA and isAccesssFow are two custom attributes i am using.
requiresMFA to stop MFA for few providers
isAccessFlow is to avoid MFA in acquire token popup.I am passing this in request object from frontend while calling acquireTokenPopup when silent calls are failing with any of the above errors.
Please let me who how i can avoid getting those errors. These attributes i used as a temporary workaround.
I have some b2c SAML CustomPolicy for one SP.
Here I have some OutputClaims. Now I want to send one Claim with some logic and tried to set OutputClaimTransformations.
My scenario:
I will send the claim "ABCString: 0,1" for Croatia and "ABCString: 0,2" for germany. 0 is some default value I need to send and the next value should be country dependent.
So I created one Transformation for defaultvalue, then one for country dependent, then I merge both in one collection and will join them in one string.
I attached my XML.
Problem is: the "ABCString" is never sent to application. The OutputClaims of the ClaimsTransformations are not used at all.
All examples I found were in Extension or Base File and are using "persistent" fields. I just want to build the value on request.
Some ideas for this?
my 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_signup_signin_xxx-test"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_signup_signin_xxx-test" >
<BasePolicy>
<TenantId>xxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="ABCCollection">
<DisplayName>abc</DisplayName>
<DataType>stringCollection</DataType>
<UserHelpText>abc.</UserHelpText>
</ClaimType>
<ClaimType Id="ABCString">
<DisplayName>abcs</DisplayName>
<DataType>string</DataType>
<UserHelpText>abcs.</UserHelpText>
</ClaimType>
<ClaimType Id="DEFString">
<DisplayName>defs</DisplayName>
<DataType>string</DataType>
<UserHelpText>defs</UserHelpText>
</ClaimType>
<ClaimType Id="GHIString">
<DisplayName>ghis</DisplayName>
<DataType>string</DataType>
<UserHelpText>ghis.</UserHelpText>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="SetDefault" TransformationMethod="AddParameterToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="ABCString" TransformationClaimType="collection" />
</InputClaims>
<InputParameters>
<InputParameter Id="item" DataType="string" Value="0" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="ABCCollection" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="GetCountrySpecific" TransformationMethod="LookupValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_country" TransformationClaimType="inputParameterId" />
</InputClaims>
<InputParameters>
<InputParameter Id="CROATIA" DataType="string" Value="1" />
<InputParameter Id="GERMANY" DataType="string" Value="2" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="DEFString" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="MergeDefaultWithCountry" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="DEFString" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="ABCCollection" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="ABCCollection" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="ConvertTeamCollectionToString" TransformationMethod="StringJoin">
<InputClaims>
<InputClaim ClaimTypeReferenceId="ABCCollection" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter DataType="string" Id="delimiter" Value="," />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="ABCString" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
<ContentDefinitions>
<ContentDefinition Id="api.signuporsignin">
<LoadUri>https://api.contoso.com/test/azure/html/xxx.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Signin and Signup</Item>
</Metadata>
</ContentDefinition>
</ContentDefinitions>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<Domain>app.contoso.com</Domain>
<DisplayName>SAML2 for App</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Saml2App">
<DisplayName>SAML2 for App</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>SAML2</OutputTokenFormat>
<Metadata>
<Item Key="IssuerUri">https://xxx.b2clogin.com/xxx.onmicrosoft.com/B2C_1A_signup_signin_xxx-test</Item>
</Metadata>
<CryptographicKeys>
<Key Id="MetadataSigning" StorageReferenceId="B2C_1A_mycert"/>
<Key Id="SamlAssertionSigning" StorageReferenceId="B2C_1A_mycert"/>
<Key Id="SamlMessageSigning" StorageReferenceId="B2C_1A_mycert"/>
</CryptographicKeys>
<InputClaims/>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Saml"/>
</TechnicalProfile>
<TechnicalProfile Id="SM-Saml">
<DisplayName>Session Management Provider</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.SamlSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInSAML2App">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2App" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInSAML2App" />
<UserJourneyBehaviors>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="SAML2"/>
<Metadata>
<Item Key="PartnerEntity"><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><some definition>]]></Item>
<Item Key="client_id">xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</Item>
<Item Key="IdTokenAudience">yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy</Item>
<Item Key="XmlSignatureAlgorithm">Sha256</Item>
</Metadata>
<CryptographicKeys>
<Key Id="MetadataSigning" StorageReferenceId="B2C_1A_mycert"/>
<Key Id="SamlAssertionSigning" StorageReferenceId="B2C_1A_mycert"/>
<Key Id="SamlMessageSigning" StorageReferenceId="B2C_1A_mycert"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="GHIString" DefaultValue="123" />
<OutputClaim ClaimTypeReferenceId="extension_country" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetCountrySpecific" />
<OutputClaimsTransformation ReferenceId="SetDefault" />
<OutputClaimsTransformation ReferenceId="MergeDefaultWithCountry" />
<OutputClaimsTransformation ReferenceId="ConvertTeamCollectionToString" />
</OutputClaimsTransformations>
<SubjectNamingInfo ClaimType="Email" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" ExcludeAsClaim="false"/>
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
I don't believe any output claims transformations are invoked for a relying party technical profile.
Rather, you must invoke the output claims transformations during the user journey, such as follows.
Define a new claims transformation technical profile that invokes the new output claims transformation:
<TechnicalProfile Id="ClaimsTransformation-CreateABCStringClaim">
<DisplayName>Create ABCString Claim</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="ABCString" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetCountrySpecific" />
<OutputClaimsTransformation ReferenceId="SetDefault" />
<OutputClaimsTransformation ReferenceId="MergeDefaultWithCountry" />
<OutputClaimsTransformation ReferenceId="ConvertTeamCollectionToString" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Add a new orchestration step, before the SendClaims orchestration step, that invokes the claims transformation technical profile:
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="ClaimsTransformation-CreateABCStringClaim" TechnicalProfileReferenceId="ClaimsTransformation-CreateABCStringClaim" />
</ClaimsExchanges>
</OrchestrationStep>
I'm trying to write a custom attribute value (into AAD B2C) during a sign-up with email invitation.
It works well with AAD B2C local accounts.
It doesn't work with Google accounts.
I use a combination of :
the SocialAndLocalAccounts version of the starter pack : https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/SocialAndLocalAccounts
the "sign-up with email invitation" example provided by the community : https://github.com/azure-ad-b2c/samples/tree/master/policies/invite
Microsoft's documentation to set-up Google custom policies : https://learn.microsoft.com/fr-fr/azure/active-directory-b2c/active-directory-b2c-setup-goog-app
some custom attributes exemples : https://medium.com/the-new-control-plane/working-with-custom-attributes-in-azure-ad-b2c-custom-policies-fae1454b12bf et https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-create-custom-attributes-profile-edit-custom#next-steps
Here is the custom attribute that I use :
<ClaimsSchema>
<ClaimType Id="extension_externalid">
<DisplayName>My External Application Identifier</DisplayName>
<DataType>string</DataType>
<UserHelpText>External Application Identifier</UserHelpText>
<UserInputType>Readonly</UserInputType>
</ClaimType>
</ClaimsSchema>
The "google account sign-up with email invitation" UserJourney :
<UserJourney Id="SignUpInvitation">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<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>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GoogleExchange" TechnicalProfileReferenceId="Google-OAUTH" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityIdWithUserAttributes" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
And the technical profile that should write my custom attribute into AAD B2C but doesn't :
<TechnicalProfile Id="AAD-UserWriteUsingAlternativeSecurityIdWithUserAttributes">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
<Item Key="UserMessageIfClaimsPrincipalAlreadyExists">You are already registered, please press the back button and sign in instead.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateOtherMailsFromEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="AlternativeSecurityId" PartnerClaimType="alternativeSecurityId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="alternativeSecurityId" />
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
<PersistedClaim ClaimTypeReferenceId="mailNickName" DefaultValue="unknown" />
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />
<PersistedClaim ClaimTypeReferenceId="otherMails" />
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
<PersistedClaim ClaimTypeReferenceId="extension_externalid" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
For example, here is the technical profile that works well for local accounts :
<TechnicalProfiles>
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmailWithUserAttributes">
<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" />
<!-- Optional claims. -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
<PersistedClaim ClaimTypeReferenceId="extension_externalid" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Note that I use an id_token_hint to set the extension_externalid value :
<ClaimsProvider>
<DisplayName>My ID Token Hint ClaimsProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="IdTokenHint_ExtractClaims">
<DisplayName>My ID Token Hint TechnicalProfile</DisplayName>
<Protocol Name="None" />
<Metadata>
<Item Key="METADATA">https://XXXXXXXXXXXXX.azurewebsites.net/.well-known/openid-configuration</Item>
</Metadata>
<OutputClaims>
<!--Sample: Read the claims from the id_token_hint-->
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="state" />
<OutputClaim ClaimTypeReferenceId="extension_externalid" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I checked that extension_externalid value is correct in the TechnicalProfile "SelfAsserted-Social" (adding it temporarily as input and output claim).
I expect the provided extension_externalid value to be writen in AAD B2C. So I can access it through API Graph AAD or in the sign-in JWT token.
Any help please?
I think that the problem was in my 5th orchestration step ...
I was using the "SelfAsserted-Social" technical profile (from the starter pack - https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/ee4832db16a226416eac1de3bd96d4a5eacff790/SocialAndLocalAccounts/TrustFrameworkBase.xml#L761) that doesn't know my "extension_externalid" attribute.
Adding a "extension_externalid" input claim to this technical profile fixed the problem.
<TechnicalProfile Id="SelfAsserted-Social">
<DisplayName>User ID signup</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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="displayName" />
<InputClaim ClaimTypeReferenceId="givenName" />
<InputClaim ClaimTypeReferenceId="surname" />
<InputClaim ClaimTypeReferenceId="extension_externalid" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingAlternativeSecurityIdWithUserAttributes" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialSignup" />
</TechnicalProfile>
Thanks to #Thomas and #ChrisPadgett for their great help !!
I have B2C custom policy signin UserJouney which checks to see if the user requires a password reset on their first logon. We are using an extension attribute to do this as B2C has a bug where the "forceChangePasswordNextLogin" value prevents the user from logging in at all.
Here is the sign in user journey.
<UserJourney Id="SignUpOrSignInSaml">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninUsernameExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninUsernameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonUsernameExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonName" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when in the token. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>extension_ChangePasswordRequired</Value>
<Value>true</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordChangeUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="UpdatePasswordResetValue" TechnicalProfileReferenceId="LocalAccountUpdatePasswordResetStateValue" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2AssertionIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Step 4 in the UserJourney evaluates whether the extension attribute "extension_ChangePasswordRequired" is set to "true" and will prompt the user to change their password if it reads "true". This is working fine.
Step 5 is then used to update the extension attribute to something other than "true" so the user isn't prompted again at next login however doesn't seem to be working.
Here is my "LocalAccountUpdatePasswordResetStateValue" TechnicalProfile
<TechnicalProfile Id="LocalAccountUpdatePasswordResetStateValue">
<DisplayName>Update Password Set Value</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" Required="true" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="SetPasswordResetStatus" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
And here is the Output claims transformation that it is calling
<ClaimsTransformation Id="SetPasswordResetStatus" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="abc123" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
The policies pass validation at time of upload however doesn't set the extension attribute on the user after a password reset.
Does anyone know what I'm doing wrong here or if there is a better way of achieving this?
-----Update-----
I'm successfully able to write a value to a different extension attribute via a persisted claim as seen here
<TechnicalProfile Id="AAD-UserUpdateStateValue">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="objectId" />
<!-- Optional claims -->
<PersistedClaim ClaimTypeReferenceId="extension_Flag" DefaultValue="abc1234567"/>
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
However as mentioned by Chris in this post this doesn't work if I have read the claim in a previous step.
The DefaultValue attribute is effective if and only if the claim value isn't set.
To force the use of a default value, set the AlwaysUseDefaultValue attribute to true:
<PersistedClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" DefaultValue="true" AlwaysUseDefaultValue="true" />
In your particular case, you should set the extension_ChangePasswordRequired claim to this default value in the AAD-UserWritePasswordUsingObjectId technical profile as the new password is written:
<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" />
<PersistedClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" DefaultValue="true" AlwaysUseDefaultValue="true" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
You can then remove orchestration step 5 from the user journey.