I am trying to set up a custom B2C policy according to sample available at: https://github.com/azure-ad-b2c/samples/blob/master/policies/force-password-reset-after-90-days/policy/TrustFrameworkExtensions.xml
The problem I face is that on every sign in pass change is prompted. I narrowed down the issue to
InputClaim isPasswordResetOnPresent always returned as false from where output claim "skipPasswordReset" is also FALSE every time
and i believe this is my failing piece
<!--Sample: Check if extension_passwordResetOn is existed in user account. -->
<ClaimsTransformation Id="CheckIfPasswordResetOnPresent" TransformationMethod="DoesClaimExist">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_passwordResetOn" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isPasswordResetOnPresent" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
This is evaluated in my Azure AD B2C trace as:
InputClaim extension_passwordResetOn undefined
I also followed all recommendations as per this similar question
Azure B2C: Where to Create DateTime Extension Attribute to be used in Custom Policy- Portal or Custom Policy or Both?
But that extension_passwordResetOn is always determined as undefined in claims transformation
In the same time I can see this extension get current date/time correctly assigned in this technical profile
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<InputClaimsTransformations>
<!--Sample: Get current date and time -->
<InputClaimsTransformation ReferenceId="SetPasswordResetOn" />
</InputClaimsTransformations>
<PersistedClaims>
<!--Sample: On sign-up, set the 'password reset on' extension attribute with the current date and time -->
<PersistedClaim ClaimTypeReferenceId="extension_passwordResetOn" />
</PersistedClaims>
</TechnicalProfile>
Related
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}
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.
I'm using username in custom policies for authentication, either email or phone can be used as user name: signInNames.emailAddress or signInNames.phoneNumber. Now i need to enable profile edit, there user change the email and phone.
Problem i'm getting here is it saying the user already exist. Does B2C allows to change the user name or is there anything wrong in this profile?
Here is the technical profile to update profile:
<TechnicalProfile Id="AAD-UserWriteProfileUsingObjectId">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<PersistedClaim ClaimTypeReferenceId="signInNames.phoneNumber" />
<!-- Optional claims -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
I have tested it and I can change the user name successfully.
You can refer to Use the new extension property or custom attribute in a user journey to configure it. Just ignore the step 3 because it's not a custom attribute.
When I change the currently username to an exist username, it will show:
A user with the specified ID already exists. Please choose a different
one.
So make sure the username you want to modify to does not exist in the tenant.
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.
I am using starter pack of custom polices with SocialAndLocalAccounts pack.
It is working fine for me.
But I am facing one issue.I need to get email as claim after successfully login.
I am getting email as claim, once user has been been signed-up and redirects back immediately to application.
but I am not getting it when a user simply signs-in.
How can I get that?
where do I need to write an Output Claim to get the value of email in claim?
Kindly help me.
Thanks
For Chris Padgett's answer, you can add other emails (Alternate email) into the claim.
If you just want to add email claim from the SignIn name into the token, you can just take following steps:
Open your SignUporSignIn.xml file
Replace <OutputClaim ClaimTypeReferenceId="email" /> with <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
Save this SignUporSignIn.xml file and upload it to Azure AD B2C to overwrite the policy.
Run the SignUporSignIn policy to test it.
Here is my test result, you can see the email claim in the token:
Hope this helps.
Following describes how you can save, load, and then issue the otherMails claim as emails from the sign-up/sign-in and password reset policies.
When writing a local account: You must create the otherMails claim from the email claim using the CreateOtherMailsFromEmail claims transformation and then persist the otherMails claim in the AAD-UserWriteUsingLogonEmail technical profile:
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
...
<IncludeInSso>false</IncludeInSso>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateOtherMailsFromEmail" />
</InputClaimsTransformations>
<InputClaims>
...
</InputClaims>
<PersistedClaims>
...
<PersistedClaim ClaimTypeReferenceId="otherMails" />
</PersistedClaims>
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
...
</TechnicalProfile>
You must then pass the otherMails claim out from the LocalAccountSignUpWithLogonEmail technical profile that is invoked to register a local account:
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
...
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
</TechnicalProfile>
When writing a social account: The otherMails claim is already created from the email claim and then persisted in the AAD-UserWriteUsingAlternativeSecurityId technical profile.
You must then pass the otherMails claim out from the SelfAsserted-Social technical profile that is invoked to register a social account:
<TechnicalProfile Id="SelfAsserted-Social">
...
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
</TechnicalProfile>
When reading a local or social account: The otherMails claim is already read in the AAD-UserReadUsingObjectId, AAD-UserReadUsingEmailAddress, and AAD-UserReadUsingAlternativeSecurityId technical profiles.
You must then pass the otherMails claim out from the LocalAccountDiscoveryUsingEmailAddress technical profile that is invoked to recover a local password:
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
...
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
</TechnicalProfile>
To issue the otherMails claim as emails from the sign-up/sign-in and password reset policies: You must add the otherMails claim as <OutputClaim /> to the relying party policies:
<RelyingParty>
...
<TechnicalProfile Id="PolicyProfile">
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="otherMails" PartnerClaimType="emails" />
</OutputClaims>
</TechnicalProfile>
</RelyingParty>
Another option that is working for me was to extend AAD-UserReadUsingObjectId so that it copies the signInNames.emailAddress claim into email. That brought sign-in into alignment with our other journeys/sub-journeys for integrated sign-up, password reset, and social log-in -- which each populate email during first log-in/sign-up.
All I needed to do was add this to TrustFrameworkExtension.xml (under <ClaimsProviders>):
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<OutputClaims>
<OutputClaim
ClaimTypeReferenceId="email"
PartnerClaimType="signInNames.emailAddress"
/>
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>