Find existing user by email in Azure Active Directory B2C Custom Policy - azure-ad-b2c

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?

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.

Identity Experience Framework and social accounts

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.

Multi tenant Azure B2C Login - how to get external user email address

I am struggling to figure out how to configure Azure B2C for multi-tenant authentication, in particular getting access to the email address of a user that is logging in via an external Azure AD (we're interested in allowing our customers to log in either via a "Local Account" (email address, managed by B2C) or their own Azure AD).
A key part of the issue I am trying to result is the passing of the logged in users email address through to a REST endpoint where our application needs to do some things internally to inject additional application specific claims, which are used later on. Apart from our REST endpoint receiving the email address, everything else is working.
I've got a "Common AAD" technical profile setup like this:
<TechnicalProfile Id="Common-AAD">
<DisplayName>Work Account</DisplayName>
<Description>Login with your Work Account</Description>
<Protocol Name="OpenIdConnect"/>
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration</Item>
<Item Key="client_id">my_client_id</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid email profile</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AADAppSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" AlwaysUseDefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="signInName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="upn" PartnerClaimType="upn" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin"/>
</TechnicalProfile>
In the orchestration, I am instructing B2C to pass in a bunch of these claims to an application-hosted REST API so that we can do our internal processing:
<TechnicalProfile Id="REST-GetProfile-Dev">
<DisplayName>Do some custom logic</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://the-endpoint.com</Item>
<Item Key="AuthenticationType">None</Item>
<!-- REMOVE the following line in production environments -->
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="objectId" />
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="sub" />
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<InputClaim ClaimTypeReferenceId="userPrincipalName" />
<InputClaim ClaimTypeReferenceId="displayName" />
<InputClaim ClaimTypeReferenceId="otherMails" />
<InputClaim ClaimTypeReferenceId="upnUserName" />
<InputClaim ClaimTypeReferenceId="alternativeSecurityId" />
<InputClaim ClaimTypeReferenceId="upn" />
<InputClaim ClaimTypeReferenceId="signInName" />
<InputClaim ClaimTypeReferenceId="socialIdpUserId" />
<InputClaim ClaimTypeReferenceId="identityProvider" />
<InputClaim ClaimTypeReferenceId="authenticationSource" />
</InputClaims>
<OutputClaims>
<!-- bunch of app specific claims -->
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
However, I can never seem to get an email address, or anything that contains the email address of the logged in user passing through.
I've tried to track through the processing that's defined in the Custom Policy XML files, and it's challenging. To be honest, I've been researching this and have tried adding all kinds of additional claims to outputs from various steps, but it's just not working for me.
Any help in detailing how to get the email address of a user logged in via an external Azure AD passed into a REST orchestration step would be much appreciated.
Thanks.
** Big Edit **
In response to Jas Suri, I have reset everything, applied the changes below as suggested, but am still not seeing this work.
Here's my TrustFrameworkBase.xml:
TrustFrameworkBase.xml
Here's my TrustFrameworkExtensions.xml:
TrustFrameworkExtensions.xml
Here's my Relying Party (SignInSignUpMulti.xml) file:
SignInSignUpMulti.xml
Now, looking at my scenarios:
When I sign in using a "local" account, I see this type of information pass through to my rest endpoint during the user journey:
{
"objectId": "1e91bfba-17a1-43b6-a451-9896fc3c1061",
"signInNames.emailAddress": "email#example.com",
"displayName": "User DispName",
"signInName": "email#example.com",
"authenticationSource": "localAccountAuthentication"
}
That's perfect. I can take this information and gather additional claims to return and all works exactly as I want.
When I sign in as an AD Account that's attached to my org, I get this:
{
"objectId": "a_guid",
"sub": "Not supported currently. Use oid claim.",
"userPrincipalName": "cpim_a_guid#TENANT.onmicrosoft.com",
"displayName": "ThisIs Correct",
"upnUserName": "14218711-5dd1-4a81-8e04-77bd08298aaf",
"alternativeSecurityId": "{\"type\":6,\"identityProvider\":\"https://login.microsoftonline.com/a_guid/v2.0\",\"key\":\"a_key\"}",
"identityProvider": "https://login.microsoftonline.com/a_guid/v2.0",
"authenticationSource": "socialIdpAuthentication"
}
I am missing an email (or the users Sign In) address.
And, the same happens when I try to sign in as an external AD:
{
"objectId": "a_guid",
"sub": "Not supported currently. Use oid claim.",
"userPrincipalName": "cpim_a_guid#TENANT.onmicrosoft.com",
"displayName": "ThisIs Correct",
"upnUserName": "9c865de4-2898-4b18-998b-7fa151f6623d",
"alternativeSecurityId": "{\"type\":6,\"identityProvider\":\"https://login.microsoftonline.com/a_guid/v2.0\",\"key\":\"a_key\"}",
"identityProvider": "https://login.microsoftonline.com/a_guid/v2.0",
"authenticationSource": "socialIdpAuthentication"
}
If I can work out how to pass through the email address or signin address, then I would be extremely happy.
Event during debugging, if I cause the user to be signed in anyway, I inspect the User.Identity, and while I see claims that my rest api is returning during the journey, I still don't have any claim that resembles the email address I am expecting (hoping) to see.
I can definitely work either way - an email address passed to the REST API, or the email address appearing in the final claim set that the application receives.
Any assistance would be much appreciated.
From Azure AD, all users will come back with a unique_name claim, which is the UPN in their Azure AD. You could also rely on this. If you rely on the email claim from AAD, it will only be present if the user has an Exchange Online inbox. You also have to set it up as an optional Azure AD claim in the AAD Multi Tenant App registration.
Usually the UPN and Email are the same in an Azure AD. So in the AAD technical profile, you could add this output claim to capture the AAD UPN:
<OutputClaim ClaimTypeReferenceId="aadUPN" PartnerClaimType="unique_name"/>
Then in the relying party secion, add this output claim:
<OutputClaim ClaimTypeReferenceId="aadUPN" PartnerClaimType="UPNfromAAD"/>

Continue Azure B2C user journey on authentication failure

I am creating a custom user journey using Azure B2C Identity Experience Framework. My issue is that I want to continue the user journey when authentication fails. However, it appears that authentication failure is interpreted as an exception, which causes the journey to terminate.
This journey is intended to accommodate a just-in-time account migration process from a legacy idenity provider to B2C.
The flow that I am seeking to accomplish is:
Attempt authentication using a B2C sign in form
On authentication failure, query a REST API to determine if the user's email address exists in the legacy system
If email address exists, present user with a B2C signup form
Is this scenario even possible?
I am not sure if there is a way to continue on full authentication failure, but you might not need to do that if just checking the existence of the user's account is enough.
You can check if the entered username exists in B2C without attempting authentication. Setting the RaiseErrorIfClaimsPrincipalDoesNotExist metadata to false allows the B2C policy to continue if the user does not exist in the directory. You can then take the entered username and continue with other technical profiles.
I used the below snippet as a validation technical profile and if the object ID is found, I run the login-NonInteractive profile, if not, I run a custom authentication profile
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-NoError">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="extension_isMigrated" DefaultValue="False" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="accountEnabled" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>

Change user name using custom policy in Azure AD B2C

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.

Resources