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

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

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.

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>

Azure B2C: Custom claim isn't written into AAD via custom policy

It seems that I've hit a road block when it comes to writing custom claims to Azure Active Directory (AAD). I'm trying to write the organization into ADD, but it appears that when I query the users via Graph API, I don't see any trace of the organization data. I'm wondering if there's something off with how I attempted to write the data or there's a techincal detail that I'm not aware of that can cause this issue?
Here's the custom claim that I want to save to AAD.
<ClaimType Id="extension_organization">
<DisplayName>Organization Name</DisplayName>
<DataType>string</DataType>
<UserHelpText>Name of admin's organization.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
And here is where I'm writing the claims (it's pretty much what you would see in the examples):
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<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_organization" />
</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>
On an interesting note, it seems that not even the e-mail can be seen.
When querying the Graph API for custom/extension attributes, you will need to make sure you select the extension attributes with the following syntax:
extension_{b2cExtensionsAppId}_organization
Where {b2cExtensionsAppId} is the Application/Client ID for the application in your B2C tenant that is automatically generated:
b2c-extensions-app. Do not modify. Used by AADB2C for storing user data.
Edit - Remove the dashes (-) from the Extensions Application/Client ID
79af1ae0-cacb-401a-9a42-1f2178adc0ef gets converted to 79af1ae0cacb401a9a421f2178adc0ef.
Example:
b2c_79af1ae0cacb401a9a421f2178adc0ef_organization

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