I have been slowly working through a custom policy to handle a JIT migration of an external IDP users to Azure B2C. It has been a long journey because there are many complexities in sorting out the components and the configuration steps using XML is a bit of a challenge. I am making some progress.
The basic use case is this: At sign in, the user/password is first checked against the B2C AD and if the user already exists just continue to sign in. If the user isn't in B2C AD, then check the external IDP with a REST call. I planned the response from the external IDP to be used in a binary way: If I get a 200 response, then add the user to B2C AD. If an error is returned then the user will be told to create an account.
I started with a couple of samples from a JIT Migration that I found. I had some difficulty navigating all the components with the social media logins, etc that I then reverted back to the "Local Accounts" sample found in the custom policy starterpack. I got the basic steps to work with no modification. I then added the technical profiles for the REST calls to the extensions file. I used the extension from one of the JIT migration samples and at least got the uploads to work. I am able to present a login prompt.
These are the scenarios and responses I see: 1) A user that I know is in B2C returns an error of "Unable to validate the information provided." 2) A user that I know is in the external IDP but not in B2C returns "We can't seem to find your account". 3) If I try a completely made up account that exists in neither, I see "The claims exchange 'REST-UserMigration-LocalAccount-SignIn' specified in step '1' returned HTTP error response with Code 'BadRequest' and Reason ''. "
So the question/struggle is how do I best sort out what is failing in each step. I have added app insights to the environment. I do see the traces in VS Code. But what should I look for? Here is the short responses ("Exceptions") for each of the errors listed above:
Scenario 1: The claim type "signInNames.emailAddress", designated as the identifier claim type, could not be found in the claims collection for the claims principal in tenant id "B2C_1A_JITMigration_signup_signin"
Scenario 2: A user with the specified credential could not be found.
Scenario 3: ErrorCodes: AADB2C90075
Rather than post of the code, here is the link to what I copied: https://github.com/azure-ad-b2c/user-migration/blob/master/jit-migration-v2/policy/TrustFrameworkExtensions.xml I can post up my exact code but basically I changed the tenant and the REST call. The Technical profile for the REST call snippet is:
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username"/>
<InputClaim ClaimTypeReferenceId="password" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="needToMigrate" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="newPassword" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
The input claims are fine but I am not sure how to modify the output claims. The REST API returns a single value of an authentication token which I don't need but is an indication that the authentication succeeded. It returns an error if the authentication fails. Basically an HTTP 200 is good, an HTTP 400 means the user doesn't exist. The goal is to use that API call as a binary indicator to either migrate or not migrate the user. Perhaps I cannot use the API in that way.
My hope is to solve each scenario step by step. Figure out why the scenario 1 doesn't work although the credential are correct. Then figure out why scenario 2 is correctly identifying that the user isn't found but wasn't automatically migrated. Finally, to figure out how to manage the 400 error returned by the API when the user doesn't exist in the external IDP.
I might need to tweak the user journeys or orchestration steps? Taking baby steps here.
Thanks
Related
My OIDC claims provider (Okta) provides the given_name and family_name values to my OIDC test harness app.
My Azure B2C claims provider uses the same scopes as my test app, but I can't get the given_name and family_name to be added to the B2C claim,
Scopes used when calling Okta CP:
<Item Key="scope">openid profile email</Item>
OutputClaims mapping in Okta CP:
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" DefaultValue="default value from input ClaimsProvider: email"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" DefaultValue="default value from input ClaimsProvider: givenName"/>
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" DefaultValue="default value from input ClaimsProvider: surname"/>
This configuration doesn't seem to get the values for these two claims. It does get the "name" and "email" values, so I feel confident the scopes are being honored. Using DefaultValues to debug, I see this in the Azure SAML test app.
SAML Login Success
Attribute Value
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress JoeBlow#xyz.com
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name Joe Blow
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname default value from input ClaimsProvider: givenName
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname default value from input ClaimsProvider: surname
http://schemas.microsoft.com/identity/claims/userprincipalname JoeBlow#xyz.com
Might be to late for the OP, but we just ran into this as well. It took us quite a while to find a solution.
Problem was not (in our case) the Azure AD B2C policy.
But instead the issue was with the Azure AD app registration (used for Ms + AAD accounts).
family_name+ given_name are optional claims
For those claims to be returned two things need to happen:
profile scope must be requested
configure the Azure AD app registration to return these 2 optional claims
This was the step we were missing
Here is the link to the doc explaining how to ensure an Azure AD app registration returns optional claims (its straight forward and only took 1min todo):
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims?WT.mc_id=AZ-MVP-5003445#configuring-optional-claims
I have an Azure B2C tenant I'm setting up using policies and I'm facing the following problem. One of my social providers is configured as an Azure Active Directory but, when getting the token, the email is not included.
I have other providers like Google or LinkedIn where I've been able to collect the email using these OutputClaims inside the TechnicalProfile of each ClaimProvider.
Google
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
LinkedIn
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="elements[0].handle~.emailAddress" />
I'm wondering if there is a similar approach to what I need but the Azure Active Directory provide offers an email's PartnerClaimType.
I checked the doc for this kind of provider but found nothing. I checked as well the optional claims set and the email is mentioned but got no clue how to make it work.
Thank's for your time!
Try
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="unique_name" />
https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens#claims-in-an-id_token
I have Azure B2C with custom policies with Local Login and Microsoft Account login enabled. I have started with the starter pack and made some modifications to add my custom logic for validate and add additional claims as explained here.
Everything works fine with Microsoft Account. But I am facing issues with Local Account Sign in.
email claim is only populated when the user signup but not on sign-in. In case of sign-in the email is part of "signInNames.emailAddress" claim. I tried making changes as explained here and here. I would like the email to be populate in email claim as my API uses this claim.
Additional calims returned from my REST API are not added to token only for Local Login. They are added for Microsoft Account.
thank you.
Update: For point 2, its a problem with my policy file and is now fixed.
There is a simple method to return email claim.
Just replace <OutputClaim ClaimTypeReferenceId="email" /> with <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" /> in your SignUporSignIn.xml file.
You need to sign up new local user and then sign in to test it. You will see the email claim.
In fact, this solution has been provided by #Wayne Yang in the post you shared.
I am using the techniques described in AD B2C->Automation->Account Management to get an Access token for an application. This is working fine, but I want to add some custom claims to the returned token. Is there some way I can use Custom Policies to add custom claims when an application requests a token?
Note: I already have custom policies that I use for interactive signons, which add claims to the returned token. I would like to somehow use these in a non-interactive mode.
I already have custom policies... I would like to somehow use these in a non-interactive mode.
For non-interactive authentication/authorization w/ B2C that would the ROPC (Resource Owner Password Credentials) flow.
In the relying party file you can output custom claims:
<OutputClaim ClaimTypeReferenceId="sub" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="myCustomClaim" DefaultValue="" />
Why we need multiple token requests:
We're using implicit flow for our SPA to get access tokens from Azure AD B2C to access APIs that are protected by the B2C. We need to access multiple APIs and those are all registered as different applications in B2C, hence different audience for each. Since they are all different audience, B2C doesn't support a single token request for multiple audiences, so we'd have to make multiple token requests.
Background on B2C setup
We support local account login, as well as social login which is using our other Azure AD identity provider. We're also using custom policies for our B2C (identity experience framework)
The issue
The issue happens for user using the Azure AD login social login. The user has logged in before.
When multiple requests are made, we noticed the following network trace in google chrome:
The trace above showing:
Line 1 & 2 are the token request to B2C authorize endpoint for 2 different api/scope/audience.
Line 3+4 & line 5+6, those are redirects to login.windows.net and login.microsoftonline.com both as 1 set for a particular api/scope/audience.
Line 7 & 8 are both the the response (id token) form post back to B2C. The line 7 returns a bad request response from the form post.
The questions
Why the need to redirect back to login.windows.net or login.microsoftonline.com? Since the user has logged in before, shouldn't he has a valid session and thus B2C can just return the token requested?
Can B2C support concurrent token request (or login) from the same browser for social login identity? We're suspecting this is due to the auth state that B2C expect from social login is only one and unique, so concurrent login causes this to override each other which then cause the other request to be invalid. There is no details at all on the bad request response. It just shows a blank page with "Bad request" text.
-- Update March 5th, 2019 --
After some tinkering on B2C custom policies, I've managed to suppress the redirects, after having logged in once, by changing the following:
<TechnicalProfile Id="SM-SocialLogin">
<DisplayName>Session Mananagement Provider</DisplayName>
<!--Changed to this provider instead of ExternalLoginSSOSessionProvider-->
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.DefaultSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="alternativeSecurityId" />
<PersistedClaim ClaimTypeReferenceId="objectId" />
... removed for brevity ...
<PersistedClaim ClaimTypeReferenceId="groups" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectIdFromSession" DefaultValue="true"/>
</OutputClaims>
</TechnicalProfile>
Changes made is to use default session provider.
Why the external session provider would not suppress re-authenticating though? The metadata AlwaysFetchClaimsFromProvider set to false would not suppress re-authenticating as well.
But going with this workaround cause us another problem which is asked in a separate question.