Azure B2C Using Claims Resolvers in ClaimsTransformation - azure-ad-b2c

Is it possible to somehow use claims resolvers in a ClaimsTransformation?
This is what I currently have (note the InputParameter):
<ClaimsTransformations>
<ClaimsTransformation Id="GenerateRequest" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="email_address" />
</InputClaims>
<InputParameters>
<InputParameter Id="content.template_id" DataType="string" Value="template-en"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
This is what I'd prefer to have, but doesn't work (Note the InputClaim):
<ClaimsTransformations>
<ClaimsTransformation Id="GenerateRequest" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="email_address" />
<InputClaim ClaimTypeReferenceId="content.templateId" TransformationClaimType="template_id" DefaultValue="template-{Culture:LanguageCode}" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
Is there any way to dynamically generate either an input parameter or input claim to use the {Culture:LanguageCode} claim resolver value? Or perhaps some other indirect way to build up the value of the template_id so that it can choose a language-dependent template?

Related

Assign value to claim through ClaimsTransformation

What is the recommended way to simply assign a default value to a claim through ClaimsTransformations?
For instance:
// Validation failed: The 'DefaultValue' attribute is not declared.Schema validation error
<!-- Assign the true to 'extension_isProfileComplete' claim whenever users fill in all required info at signup -->
<ClaimsTransformation Id="ProfileIsCompleted" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_isProfileComplete" TransformationClaimType="inputClaim"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_isProfileComplete" DefaultValue="true" AlwaysUseDefaultValue="true" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
I know I can achieve the same using a TechnicalProfile only for that but it seems overkill.
I use:
<ClaimsTransformation Id="CreateXXX" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="Some value"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="xxx" TransformationClaimType="createdClaim"/>
</OutputClaims>
</ClaimsTransformation>

Is there a way to conditionally copy claims in custom policies?

We want to return the user email as one of the claims after signin. However, the email is in different claims depending on how the user signed in or even after they just signed up. Is there a way to merge them all into a single email claim?
Our current workaround is to merge them in the very last step by just specifying the claim multiple times. However, when creating a SAML policy, this will create multiple claims and cause errors. We want to merge at an earlier stage.
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="trustFrameworkPolicy" Required="true" DefaultValue="{policy}" />
<!-- If the user used social sign in -->
<OutputClaim ClaimTypeReferenceId="email" />
<!-- Required when the user just signed up and still has the "sign up session" -->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
<!-- Get email from local account - must not set default value -->
<OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
What I tried so far:
<ClaimsTransformation Id="CopySignInNameToEmail" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="inputClaim"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
However, this will fail if the input claim is empty or not yet present.
• You can try using the ‘FormatStringMultipleClaims’ type of claim transformation method as below by giving input claims as multiple attributes and then merging the output claims as desired in a single string as below: -
<ClaimsTransformation Id="CreateEmailIDFromFirstNameDisplayNameLastName"
TransformationMethod="FormatStringMultipleClaims">
<InputClaims>
<InputClaim ClaimTypeReferenceId="givenName"
TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="surName"
TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0} {1}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="outputClaim"
/>
</OutputClaims>
</ClaimsTransformation>
• The above transformation uses C# String.Format method in which the input claims act as string format {0} and {1} parameter which supports string claims transformation expressions.
• Please find the below links for more information: -
https://learn.microsoft.com/en-us/azure/active-directory-b2c/string-transformations
https://learn.microsoft.com/en-us/azure/active-directory-b2c/claimstransformations

How to fetch only one user from Azure AD B2C using a custom policy with different user matches

I have a validation technical profile that checks if there is an existing user with the same company custom attribute during sign up and returns an error. It works great if there is just one user that matches the company name but throws an error when there are multiple which is possible.
Exception is application insight is:
Only one retrieved principal can be returned.
<TechnicalProfile Id="AAD-CheckDuplicateCompany">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_company" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" DefaultValue="NOTFOUND" AlwaysUseDefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="objectIdNotFound" DefaultValue="NOTFOUND" AlwaysUseDefaultValue="true" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertObjectIdAADUserObjectIdNotFoundAreEqual" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<ClaimsTransformation Id="AssertObjectIdAADUserObjectIdNotFoundAreEqual" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="objectIdNotFound" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase" />
</InputParameters>
</ClaimsTransformation>
AAD-CheckDuplicateCompany is used as a validation technical profile in LocalAccountSignUpWithLogonEmail, so it will not insert the user if there is at least one user that exists with the same company attribute. Is there a way to get just one user match?
Not possible. It’s only supported to use an input claim that uniquely identifies an account.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile#inputclaims
You need to make your own REST API call and perform your custom logic there.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-rest-api-claims-exchange?pivots=b2c-custom-policy

AADB2C Custom Policies: The data type of the claim with does not match the DataType of ClaimType specified in the policy

I've been creating an Invitation policy on AADB2C, this is secured with a JWT as per the the WingTipGames examples provided by Azure.
My example is slightly different because I'm using Azure Functions instead of a .NET app.
I've enabled Application Insights on my custom policy to get a bit more information on why it's failing after login. I'm successfully redirected to my social login, but after logging in it looks like it's having an issue with User creation. I'm getting this error:
The data type 'Boolean' of the claim with id 'verified_email' does not match the DataType 'String' of ClaimType with id 'extension_VerifiedEmail' specified in the policy.
Here's a snippet from my RelyingParty
<TechnicalProfile Id="Invitation">
<DisplayName>Invitation</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputTokenFormat>JWT</InputTokenFormat>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_ClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
This is what my ClaimType looks like in TrustFrameworkBase.xml
<ClaimType Id="extension_VerifiedEmail">
<DisplayName>Verified Email</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="verified_email" />
<Protocol Name="OpenIdConnect" PartnerClaimType="verified_email" />
</DefaultPartnerClaimTypes>
<UserInputType>Readonly</UserInputType>
</ClaimType>
This is another snippet from my Google ClaimsProvider in TrustFrameworkBase.xml
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateEmailFromVerifiedEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_VerifiedEmail" Required="true" />
...
</OutputClaims>
Here's the ClaimsTransformation mentioned in the above code
<ClaimsTransformation Id="CreateEmailFromVerifiedEmail" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
Finally, here's a snippet from where I'm constructing the JWT that's passed over to the custom policy.
var verifiedEmailClaim = new Claim("verified_email", email);
instancePolicyClaims.Add(verifiedEmailClaim);
I've decoded the JWT manually and I can verify that the claim exists in the JWT called verified_email and the value is correct. I'm not sure what's going on or where Boolean is coming from in the error message mentioned above.
This was getting caused by some <InputClaims> and <OutputClaims> on my Google ClaimsProvider.
I added them as per the spec for WingTipGames but the document they included in the git repo was only for local accounts.
I removed the following lines and it's now working.
</CryptographicKeys>
<!-- <InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateEmailFromVerifiedEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims> -->
<OutputClaims>
<!-- <OutputClaim ClaimTypeReferenceId="extension_VerifiedEmail" Required="true" /> -->
</OutputClaims>

Why are claims being flagged as not supported in my custom policy?

I'm switching our app from using built-in user flows to custom policies so that we can enable some features that we need like account linking and REST integration.
My TrustFrameworkBase.xml and TrustFrameworkExtensions.xml policy files both upload fine. But when I try uploading the relying party file I'm hitting a validation error that I can't explain:
Validation failed: 2 validation error(s) found in policy "B2C_1A_SIGNUP" of tenant "HyperProofLocalDev.onmicrosoft.com".Input Claim 'alternativeSecurityIds' is not supported in Azure Active Directory Provider technical profile 'AAD-UserWriteUsingAlternativeSecurityId' of policy 'B2C_1A_SignUp'.Input Claim 'emails' is not supported in Azure Active Directory Provider technical profile 'AAD-UserCreateEmailsClaim' of policy 'B2C_1A_SignUp'.
I followed guidance online such as this post to add support for these claims. Haven't been able to determine why B2C thinks these are unsupported.
Here's what I have for emails in TrustFrameworkBase.xml:
<ClaimType Id="emails">
<DisplayName>Emails</DisplayName>
<DataType>stringCollection</DataType>
<UserHelpText>User's email addresses</UserHelpText>
</ClaimType>
<ClaimsTransformation Id="GetFirstOtherMail" TransformationMethod="GetSingleItemFromStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="otherMails" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="firstOtherMail" TransformationClaimType="extractedItem" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="CopyFirstOtherMailToEmails" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="firstOtherMail" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="CopySignInNamesEmailToEmails" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<TechnicalProfile Id="AAD-UserCreateEmailsClaim">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetFirstOtherMail"/>
<OutputClaimsTransformation ReferenceId="CopySignInNamesEmailToEmails"/>
<OutputClaimsTransformation ReferenceId="CopyFirstOtherMailToEmails"/>
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
And here's the relying party 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="hyperprooflocaldev.onmicrosoft.com"
PolicyId="B2C_1A_SignUp"
PublicPolicyUri="http://hyperprooflocaldev.onmicrosoft.com/B2C_1A_SignUp"
DeploymentMode="Development"
UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights"
>
<BasePolicy>
<TenantId>hyperprooflocaldev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUp" />
<UserJourneyBehaviors>
<SessionExpiryType>Rolling</SessionExpiryType>
<SessionExpiryInSeconds>86400</SessionExpiryInSeconds>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="451d3a92-fb38-4a1b-9b77-2f6572677090" DeveloperMode="true" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
<ContentDefinitionParameters>
<Parameter Name="emailAddress">{OIDC:LoginHint}</Parameter>
<Parameter Name="givenName">{OAUTH-KV:givenName}</Parameter>
<Parameter Name="surname">{OAUTH-KV:surname}</Parameter>
</ContentDefinitionParameters>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="emails" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="trustFrameworkPolicy" Required="true" DefaultValue="{policy}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
The user object has the otherMails property rather than the emails property which is why the error is occurring.
Assuming that you have declared the signInNames.emailAddress and otherMails claim types, then you must modify the AAD-UserCreateEmailsClaim technical profile, as follows, to read both the signInNames.emailAddress and otherMails properties for the user object before they are processed by the output claims transformations:
<TechnicalProfile Id="AAD-UserCreateEmailsClaim">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetFirstOtherMail"/>
<OutputClaimsTransformation ReferenceId="CopySignInNamesEmailToEmails"/>
<OutputClaimsTransformation ReferenceId="CopyFirstOtherMailToEmails"/>
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
In the AAD technical profiles you have (mentioned in the validation message), you have emails as the OutputClaim. However, such a property does not exist in AD Graph (which is used by AzureActiveDirectoryProvider). IEF is complaining because it's impossible to source its value.
When you add an OutputClaimsTransformation♧, emails claim will be created because it is an OutputClaim of the transformation. It does not need to be added to the technical profile.
This check was recently added to help policy authors understand which claims could not be sourced but because of documentation it is being switched off currently. It will be added once, based on such feedback, we can figure out how to roll it out while we can also help policy authors address the issues easily.

Resources