How to persist email, first and last names with HRD custom policy - azure-ad-b2c

I have a modified version of the CP at https://github.com/azure-ad-b2c/samples/blob/master/policies/home-realm-discovery-modern/policy/SignIn_smart_HRD.xml running in a test directory, with our company AD federated as an IdP. When I attempt to login to an app with my AD email address and my account does not exist in the B2C dir, it creates it using UserWriteUsingAlternativeSecurityId in OrchestrationStep 8. However, the new user account has only a Display Name; no fname, no lname, and, worst of all, no email. How do I ensure these elements get persisted in that step? I am not sure how to "grab hold" of them.
Ideally, I'd like the federated identity to be automatically linked to an existing local account with the same email address, without the user having to do a local login. All the examples I have seen around account linking involve either the user logging in first with a local password account or having the user's OID prepopulated. I only have the new user email addresses.
-GBS

You “grab hold” of those claims by mapping them in as output claims within your identity provider technical profile.
By default, the following claims are persisted, based on this:
<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>
So, in your identity provider technical profile, you need to map claims into the above claim names, eg:
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
Here, family_name from the Idp’s token, is mapped into the claim name, surname. Which is then persisted later in WriteUsingAlternativeSecurityId technical profile.
You could map the IdP email claim into mailNickName. Or into an extension attribute and add it as a persisted claim.
If you want auto account linking:
https://github.com/azure-ad-b2c/samples/blob/master/policies/auto-account-linking
To prevent asking the user to prove they own the account on account linking execution, remove steps 1-7 (inclusive) in HandleLinkLocalToSocial subjourney.

Related

Missing Username on users signed-up in Azure AD

I have integrated Azure authentication in my project, it working good so far but, when I checked my azure ad users the username is showing unknown, I search every where but I can't found any suitable result that tells me what is the reason and problem, Azure signin_signup policy working good, when I was testing the policy first I signup and then I sign-in, when login is successfully I decode the token of the azure token and I found both name but not azure ad user name is unknown.
• Since, you are using Azure AD B2C custom policies regarding ‘Signup and Signin’, which are working fine but when you are trying to decode the token received for information in it, you are not getting information on the ‘username’ of the signed in user, it is because of the ‘Validation Technical Profile’ section being executed before the ‘OutputClaimsTransformation’ section. Thus, when signing up with an Azure AD local user account, the ‘LocalAccountSignUpWithLogonEmail’ technical profile is used which includes ‘AAD-UserWriteUsingLogonEmail’ technical profile as the validation technical profile. As a result, if you check the ‘AAD-UserWriteUsingLogonEmail’ technical profile in your signin-signup policy, you can see that in your ‘PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />’ is stated. As a result, since the validation technical profile runs before ‘OutputClaimTransformation’, you are getting the ‘DefaultValue’ of ‘unknown’.
Thus, the claims transformation profile should be as below in your signup-signin policy: -
<ClaimsTransformation Id="CreateDisplayNameFromFirstNameAndLastName" 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="displayName" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
• Accordingly, ensure that the ‘Technical profile’ is also updated in your ‘LocalAccountSigninSignup’ custom policy as shown below and add ‘Validate-DisplayName’ technical profile as validation technical profile under ‘LocalAccountSignUpWithLogonEmail’ technical profile as mentioned below (in same order): -
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Validate-DisplayName" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
Thus, this would help you to resolve this issue of getting ‘unknown’ in the username. For more information, kindly refer the below link: -
https://learn.microsoft.com/en-us/answers/questions/713037/display-the-name-of-user-in-ad-b2c-instead-of-unkn.html

AAD Request to https://graph.windows.net method GET as request body is malformed while searching existing user with email

I am already using B2C custom policy to let employees signin using external IdP,
now I want to use my organization's Azure enterprise AD app as IdP.
All employees are already registered using external IdP but external IdP subscription will expire soon, so I need to move them to Org's Azure AD IdP.
When registered user sign in using Azure enterprise AD app with same email Id, it should find the existing users based on email and associate the existing user's objectId with new Azure IdP. It should not create a new ObjectId for same email user for new IdP.
In my case it is creating new objectId for different policy and I don't want new objectId created for different IdP because then it will duplicate the user in database due to new object Id Instead I want the same object Id, can you please provide info how this can be achieved?
I followed this while updating my policy - Link user journey
<TechnicalProfile Id="AAD-UserReadUsingEmailId-Link">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateOtherMailsFromEmailFromADAccount" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="displayName" PartnerClaimType="displayName" />
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="otherMails" />
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
but when I am searching with email I am getting below error in app insight log:
"Statebag": {
"PROT": {
"c": "2021-07-13T08:10:25.3600788Z",
"k": "PROT",
"v": "AAD Request to https://graph.windows.net/494db189-1a11-4de4-a4d0-7ba7003b4fbd/users?api-version=1.6-integrationOnly&%24filter=logonIdentifiers%2fany(x%3ax%2fvalue+eq+%27Prashant.Kumar%40gmail.com%27) using method GET as request body is malformed.\r\nResponse: \n{\"odata.metadata\":\"https://graph.windows.net/494db189-1a11-4de4-a4d0-7ba7003b4fbd/$metadata#directoryObjects\",\"value\":[]}\r\n",
"p": false
}
Why its giving malformed request, am I doing something wrong in policy file? any suggestions?
Although your request URL seems to be well-formatted as per Azure AD Graph API requirements, there is -integrationOnly value appended to the {api-version} which can only be one of the following values:
"beta"
"1.6"
"1.5"
"2013/11/08"
"2013/04/05"
which invalidates the request URL. Remove it and try again.
One other possible cause is spaces in the URL. Spaces in the query string should be URL-encoded before sending a request.
For example, your query string,
https://graph.windows.net/494db189-1a11-4de4-a4d0-7ba7003b4fbd/users?$filter=logonIdentifiers/any(x:x/value eq 'Prashant.Kumar#gmail.com')&api-version=1.6
should be URL encoded as:
https://graph.windows.net/494db189-1a11-4de4-a4d0-7ba7003b4fbd/users?$filter=logonIdentifiers/any(x:x/value%20eq%20'Prashant.Kumar%40gmail.com')&api-version=1.6
Although this is already done as in your error message, it is worth noting since it is mentioned at the bottom of the Azure AD Graph API Supported queries, filters, and paging options page.
Finally, as a side note, Microsoft recommends that you migrate your apps to the Microsoft Graph API since Azure AD Graph is deprecated but won't be retired until the end of year 2022.
Read more about Graph API Version {api-version}

Azure B2C: Checking if another claim exists during local account creation

In addition to the usual creation of an account based on whether the e-mail used doesn't currently exist, I would like to know how I can also check if another claim value doesn't exist in Azure Active Directory.
For example, for our application, anyone who creates an account must provide an organization name. Once they signup, they are the owner of their organization group.
Before account creation, I want to check if an organization name is not associated with any other account (we're going to do sign-up via invitation if the owner wants to add people to their organization). If it doesn't exist, then create the account. Otherwise, I want to throw an error and prevent the creation of an account.
After looking through the Azure B2C technical profiles documentation, I would think that modifying AAD-UserWriteUsingLogonEmail would be my best guess.
I've tried two approaches so far. The first approach was including the input claim for the organization name. However, this just freezes the test flow:
<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" />
<InputClaim ClaimTypeReferenceId="extension_organizationName" 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_organizationName" />
</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>
The second approach was something similar, but instead using an <InputClaimsTransformations> to check if an organization name exists via the DoesClaimExist action. When using this approach, I get the following error:
Unable to validate the information provided.
Since I'm new to creating custom policies, is modifying the AAD-UserWriteUsingLogonEmail profile on the right track or do I need a completely different approach?
EDIT:
After following Barbara's links, I was able to get the validation working. However, I'm still having issues trying to prevent an account that is using an organization that is already associated with another account. It seems that using an <InputClaimsTransformations> doesn't really do anything.
The error message Unable to validate the information provided. indicates that you did not configure your custom policy correctly to be able to use custom claims.
Thus you have to follow the documentation:
Get the application properties of the extensions app
Modify your custom policy to include the application properties in the AAD-Common-technical profile
Please follow the steps and try again.

Azure AD B2C : Not able to expose companyName as token

I am using Azure AD B2C and custom policies to manage users and give sign in experience. I noticed that there is an inbuilt(not a custom extension) attribute name companyName which i am updating with user's company information. I am able to update this filed value via graph API and retrieve it respectively. Once this value is updated i am trying to include this in claims and send to client application however i am running into an issue. I have made sure to include a claim in TrustFrameworkBase policy like below
<ClaimType Id="companyName">
<DisplayName>companyName</DisplayName>
<DataType>string</DataType>
</ClaimType>
Also i have updated AAD-UserReadUsingObjectId techincal profile which is responsible for making a graph call (after successful authentication) and get all user attributes. here is definition for AAD-UserReadUsingObjectId technical profile
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="mobile" />
<OutputClaim ClaimTypeReferenceId="country" />
<OutputClaim ClaimTypeReferenceId="postalCode" />
<OutputClaim ClaimTypeReferenceId="state" />
<OutputClaim ClaimTypeReferenceId="company" />
<OutputClaim ClaimTypeReferenceId="companyName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<!--Adding custom attribute start-->
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="extension_UserGuid" />
<OutputClaim ClaimTypeReferenceId="extension_StatusFlag" />
<OutputClaim ClaimTypeReferenceId="extension_EZUserName" />
<OutputClaim ClaimTypeReferenceId="CurrentTime" />
<!--Adding custom attribute end-->
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetSystemDateTime" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
and finally i have included this companyName claim in user journey output claims.
Even after doing all this i noticed that companyName claim is not inside the token returned by azure AD B2C.
To trouble shoot further i enabled application insight for my policies and i observed a weird behavior.
When Azure AD B2C makes a graph call while executing AAD-UserReadUsingObjectId technical profile it forms a query something like below
https://graph.windows.net/f17d6207-7c3a-4d29-b802-ad5429b2a8d8/users/77669822-8034-4666-9800-8b614c0ccbfc?api-version=1.6-integrationOnly
and output of this graph api call does not include companyName attribute at all however when i make graph api call with similar query using my application id and secrete i do see companyName in the response. The only difference in the query which Azure AD B2C call vs i am calling the last part in api url. Azure AD B2C has -integrationOnly at the end where as i dont. I am not sure why graph API call response is differnet when Azure AD B2C makes it vs when i make it.
I can solve this issue by simply adding a new custom extension field and using that instead of companyName but my point is why i should create a custom attribute when one is provided out of the box and more importantly it will require me to fix about 1 million existing users.
has anyone come across this type of issue. Any help would be great!
Thanks in Advance!
CompanyName is an O365 built in attribute. B2C uses integration-only api version in the backend for its own calls as you’ve seen. The only solution is to use the extension attribute. A script to PATCH the users can fix 1million users in around 12hrs.
When using custom policy, stick to your own custom attributes, there is no real advantage trying to use default attribute names, only sometimes you find disadvantages. Custom policy only needs you to declare the attribute name as extension_ and it works just like any built in attribute.

Azure AD B2C SelfAssertedAttributeProvider

There are 4 data URIs (taking out older versions)
urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.1.0
urn:com:microsoft:aad:b2c:elements:contract:multifactor:1.1.0
urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:1.1.0
urn:com:microsoft:aad:b2c:elements:contract:globalexception:1.1.0
My biggest concern is the first one as it is overloaded, and if I put it in a concrete example of the starter pack’s password-reset journey, the first tech-profile of this journey is LocalAccountDiscoveryUsingEmailAddress
The content-def this tech profile is api.localaccountpasswordreset and the data-URI is obviously urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.1.0
So while climbing up this ladder from user-journey -- > orch.-step -- > tech-profile -- > content-def. -- > data-URI (where actually B2C prepares its own portion of HTML for the browser),
as we know, the OutputClaims in the SelfAssertedAttributeProvider indicates that these claims need to be sent back by the provider and thus will be sourced from the user. In this profile we have following output-claims.
But it is obvious that this provider will NOT prepare UI-widgets to collect values of objectId OR userPrincipalName OR authenticationSource
So in general who made this decision about which output-claims user will be prompted to fill-in ?
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
</OutputClaims>
Thanks.
Got it Chris !!! Thank you so much.
Just for the benefit of everybody, I overlooked following sentence in the doc.
"If the self-asserted technical profile contains a validation technical profile that outputs the same claim, Azure AD B2C does not present the claim to the user."
Thanks
The output claims for a self-asserted technical profile can be sourced as follows:
Collected from the end user
Defaulted to a fixed value
Returned from a validation technical profile
Returned from a claims transformation
In the following example:
The email, newPassword and reenterPassword claim is collected from the end user
The executed-SelfAsserted-Input claim is defaulted to a fixed value (so the end user isn't prompted for it)
The objectId claim is returned from the AAD-UserWriteUsingLogonEmail validation technical profile (so the end user isn't prompted for it)
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
...
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" AlwaysUseDefaultValue="true" />
...
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
...
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
...
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
...
</OutputClaims>
...
</TechnicalProfile>
The Output claims section of the Define a self-asserted technical profile in an Azure Active Directory B2C custom policy article explains this further.
Also on the same thread, may I ask one more clarification about Self-Asserted tech-profile.
What is the need of CryptographicKeys in this profile ?
for eg., how is this signature-key logically used which I am finding in the starter pack ?
Thanks.

Resources