Identity Experience Framework and social accounts - azure-ad-b2c

I developed a policy, which allows to login with username and password (B2C user) or using the Microsoft account, connecting the AD as an OpenId Identiy provider. It works fine, but when I login with my Microsoft account, the email is not set:
I guess I have to set something in the following snippet:
<TechnicalProfile Id="AAD-UserWriteUsingAlternativeSecurityId">
<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>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="alternativeSecurityId" />
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
<PersistedClaim ClaimTypeReferenceId="mailNickName" DefaultValue="unknown" />
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />
<!-- Optional claims -->
<PersistedClaim ClaimTypeReferenceId="otherMails" />
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
I tried to add <PersistedClaim ClaimTypeReferenceId="email"/> to the PersistedClaims, but I get a Bad Request with message: One or more property values specified are invalid.
On this page I see that the correct definition is mail. I also tried to define a new ClaimType and use a ClaimsTransformation to get the value from otherMails, but the field is not set.

This will not work:
<PersistedClaim ClaimTypeReferenceId="email"/>
Because there is no such attribute on the user object called "email".
What you can do is this:
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress"/>
This will then show up under userPrincipalName in the Azure Portal. It will also mean that the user cannot then sign up with that Email for a local account, essentially you create a "dual/linked account", albeit without a password.
The user then needs to reset the password on this account to access it with Username and Password, if they ever delete their Social Account at Facebook for example.
Another option is to write to otherMails
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="otherMails"/>
This might need to be an array, so use a claimTransform to build an Array first before writing it.
Do not write the email to the mail attribute, the supported attributed for B2C accounts are here.

Related

Is there a way for use UserName instead of email in Password Reset flow in Azure AD B2C?

I'm customizing the PasswordReset flow in azure ad b2c using custom policies, but i can't find a way for use UserName instead Email for restore password. I've tried to use input signInName instead of email in the technical profile AAD-UserReadUsingEmailAddress, but still shows the email in the form.
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.userName" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="accountEnabled" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
It's possible to do this with userName?
This technical profile is the implementation to READ the account. What you are trying to achieve is to show the Username text box first and foremost. To display something on screen, you need to modify a selfAsserted technical profile.
The key is to change the operating mode to Username in the selfAsserted technical profile which asks the user for their identifer (which from the starter pack is: LocalAccountDiscoveryUsingEmailAddress), the latest key name is setting.operatingMode, reference here, set it to username. Then the textbox validation will be for username.
There is a complete sample here, and you can quick deploy using this link.

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

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

Find existing user by email in Azure Active Directory B2C Custom Policy

I'm creating a custom policy in Azure AD B2C to let invited users sign in via another Azure AD (or even ADFS). The problem I have is that a new user gets created when they sign in (rather than the invited user). I have found that I have been trying to find existing user in my AAD using alternativesecurityid or objectid and both of these are not matching. So I think I need to find an existing user by email, and not any IDs. This too, is not working, because I can see my invited user's email is sitting in otherMails and mail properties (via GraphAPI), and apparently I cannot query B2C via these fields.
<TechnicalProfile Id="AAD-ReadUserByEmail">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="emailFromSocialAccount" PartnerClaimType="mail" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Only <InputClaim ClaimTypeReferenceId="emailFromSocialAccount" PartnerClaimType="signInNames.emailAddress" Required="true" /> passes validation, but this field is not having any data.
How do I find the invited user?

How to read user by claim in AAD B2C custom policy?

Is there any option to read azure AD B2C tenant users (local or social) by a claim value such as email address or by custom claim such as extension_company_user_id etc.
Actually I need something like below:
<TechnicalProfile Id="AAD-UserReadUsingClaimValue">
<Metadata>
<Item Key="Operation">Read</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="company_user_id" PartnerClaimType="extenstion_company_user_id" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="country" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_company_user_id" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
You cannot do that because the claim has no uniqueness constraint. You need to read users by the signInNames property or the userIdentities/alternativeSecurityId property, these have a uniqueness constraint.
This would be valid:
<InputClaim ClaimTypeReferenceId="userEnteredEmail" PartnerClaimType="signInNames.emailAddress" />
If extenstion_company_user_id is going to be unique, and a user identifier, write it to signInNames.companyUserId. AAD B2C will automatically register the schema extension when doing so in custom policy.
Then you can read a user like this:
<InputClaim ClaimTypeReferenceId="company_user_id" PartnerClaimType="signInNames.companyUserId" />

Resources