Assign value to claim through ClaimsTransformation - azure-ad-b2c

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>

Related

Azure B2C Using Claims Resolvers in ClaimsTransformation

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?

Azure B2C: How to use OtherMails attribute for MFA

I am using B2C custom policies which allows signup/signin with the username instead of the traditional email.
As part of the signup process, I am saving the Email in the otherMails attribute.
when choosing MFA as Email, I don't see the email field prepopulated with the email that I have on user record.
Can otherMails attribute be used for MFA email?
<ClaimsTransformation Id="CreateEmailsFromOtherMailsAndSignInNamesInfo" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="otherMails" TransformationClaimType="collection" />
</InputClaims>
<TechnicalProfile Id="AAD-UserWriteUsingLogonName">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="otherMails" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<TechnicalProfile Id="LocalAccountSignUpWithLogonName">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<TechnicalProfile Id="AAD-ReadCommon">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<RelyingParty>
<OutputClaim ClaimTypeReferenceId="otherMails" PartnerClaimType="emails" />
</RelyingParty>
The field used for MFA is "strongAuthenticationEmailAddress".
That's used by the back end so I doubt it can be changed.

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>

Resources