Mapping claims with Azure AD B2C Custom Identity Provider (OpenID Connect) - azure

Although, I've set all the claim mappings well so they match those issued by our Identity Server 3, we don't seem to have those values on Azure AD side. Name and email are claims which can be used as an example. And which is weird, this happens only with Custom Identity Provider (Open ID Connect) while for example Facebook built-in Identity Provider works well and takes those claims received from IdP. Is there anyone who made this work ever?
[EDITED]
Additionally, I have also tried to achieve this trough custom polices as it was suggested here: How to store claims from IdentityServer 3 in Azure AD B2C or just include it in tokens issued by AAD B2C. Now, I'm facing with another problem to simply connect AAD B2C to Identity Server 3 by using custom policies. Here is my TechnicalProfile definition from TrustFrameworkExnsion.xml:
<TechnicalProfile Id="IdentityServerProfile">
<DisplayName>IdentityServer</DisplayName>
<Description>Login with your IdentityServer account</Description>
<Protocol Name="OpenIdConnect"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="METADATA">https://{identity_server_hostname}/identity/.well-known/openid-configuration</Item>
<Item Key="ProviderName">https://{identity_server_hostname}/identity</Item>
<Item Key="client_id">00000000-0000-0000-0000-000000000000</Item>
<Item Key="IdTokenAudience">00000000-0000-0000-0000-000000000000</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid profile customScope</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="AccessTokenResponseFormat">json</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_IdentityServerAppSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="IdentityServer" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="tid" />
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="sub" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
Basically, after authentication on IdentityServer side, I got redirected back to my web page which initialized the sign-in and then I get this error:
AADB2C: An exception has occurred.
Correlation ID: 6797f691-4adb-4963-ad12-f31add3e1919
Timestamp: 2018-08-23 08:42:54Z
While analyzing the log on AAD B2C for the given correlation ID, I didn't find anything useful which would lead me to the possible solution.

Yesterday, after quiet a long time spent on trying so many different things, I finally realized why we were not getting all claims back on the client. They actually didn't exist in identity token but only in access token. AAD B2C uses the first one, the identity token, while doing mappings defined in custom policies and that was the whole point. In the end I had to make some small changes on IdentityServer3 side (take a look at the code below).
This is how the class which is responsible for issuing claims and generating both identity and access tokens now looks like:
public class CustomClaimsProvider : DefaultClaimsProvider
{
private readonly IIndex claimDefinitions;
public CustomClaimsProvider(
IUserService users,
IIndex<string, IClaimsDefinition> claimDefinitions)
: base(users)
{
this.claimDefinitions = claimDefinitions;
}
public override async Task<IEnumerable<Claim>> GetIdentityTokenClaimsAsync(
ClaimsPrincipal subject,
Client client,
IEnumerable<Scope> scopes,
bool includeAllIdentityClaims,
ValidatedRequest request)
{
var claims = await base.GetIdentityTokenClaimsAsync(subject, client, scopes, includeAllIdentityClaims, request).ConfigureAwait(false);
return GetAdditionalClaims(scopes, claims);
}
public override async Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(
ClaimsPrincipal subject,
Client client,
IEnumerable<Scope> scopes,
ValidatedRequest request)
{
var claims = await base.GetAccessTokenClaimsAsync(subject, client, scopes, request).ConfigureAwait(false);
return GetAdditionalClaims(scopes, claims);
}
private IEnumerable<Claim> GetAdditionalClaims(IEnumerable<Scope> scopes, IEnumerable<Claim> claims)
{
var scopesList = scopes.ToList();
var claimsList = claims.ToList();
foreach (var scope in scopesList.Select(x => x.Name))
{
if (claimDefinitions.TryGetValue(scope, out IClaimsDefinition claimDef))
{
claimsList.AddRange(claimDef.GetClaims(claims));
}
}
return claimsList;
}
}
So, the main point is, you should also override GetIdentityTokenClaimsAsync method in the class derived from DefaultClaimsProvider if you want to have some additional claims as a part of your identity token.
Big thanks to Microsoft Support which helped me a lot with troubleshooting the issue.

Related

Modify the JwtIssuer ClaimsProvider in the custom policy to remove the Refresh Token in AD B2C

I'm trying to add a custom policy without refresh token
I've modifiy the ClaimsProviders this way :
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
<Item Key="AuthenticationContextReferenceClaimPattern">None</Item>
<Item Key="token_lifetime_secs">3600</Item>
<!-- 1 H -->
<Item Key="id_token_lifetime_secs">3600</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
But it's not working.
I'm facing this js error when I try to sign in :
main.js:1 ERROR ServerError: server_error: AADB2C: Issuer technical profile 'JwtIssuer' must specify a 'issuer_refresh_token_user_identity_claim_type' to use this OAuth flow
Correlation ID: bab26044-1e53-4b4a-b5c9-d2f35030a9d7
Any ideas ?
Thanks :)
Thanks for the comment #paralight. We need to apply some workaround to achieve the same as there is no way to remove the refresh token and no direct modifications to the JWTIssuer technical profile.
Workaround:
Force a session time out by adding a tag UserJourneyBehaviors in custom policy. ex :
<UserJourneyBehaviors> <SingleSignOn Scope="Application" /> <SessionExpiryType>Absolute</SessionExpiryType> <SessionExpiryInSeconds>900</SessionExpiryInSeconds> </UserJourneyBehaviors>
Other scenario :
Claims information in the JWT token is exposed to the public. To store some sensitive information in the JWT token
Workaround: You can send claims to a REST API and send them back to B2C to encrypt.
You would create an orchestration step before the SendClaims step to send all claims to a REST API, and have the REST API respond with encrypted versions of those claims
JWT Token issuer reference https://learn.microsoft.com/en-us/azure/active-directory-b2c/jwt-issuer-technical-profile
REST API usage https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-rest-api-claims-exchange
Reference : All Technical Profiles : https://learn.microsoft.com/en-us/azure/active-directory-b2c/technicalprofiles
Reference SO Thread: Modify the JwtIssuer ClaimsProvider in the custom policy to achieve the JWE in AD B2C
Adding your comment as answer and glad to know your queries are addressed. You can accept it as answer( click on the check mark beside the answer to toggle it from greyed out to filled in). This can be beneficial to other community members.

Is there something special about B2C's givenName and surname claims? Can't read them from the OIDC ClaimsProvider

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

Registration with Azure AD B2C custom policy fails due to missing claim information

I am currently investigating the possibilities to send emails from a custom policy in Azure AD B2C through a custom email service provider. To do so, I was following this tutorial from the Microsoft documentation: https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-email-sendgrid.
Since we have been using custom policies for quite some time now, I tried to adopt the tutorial right away to our own custom policy: we use a multistep registration process with the email verification step being the first and the personal data step being the second step.
My efforts have been successful with connecting the email service provider, sending the code through it and proceed to the second step of the registration process after successfully verifing the email address.
However, when I want to complete the second step of the registration and have the user be actually created in AD I see the following error in the frontend:
Unable to validate the information provided.
To gain further insights I activated developer mode in the policy and checked the traces in Application Insights where I found the following error:
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_signup_notificationtest".
caused in the technical profile AAD-UserWriteUsingLogonEmail. However, as you can see below in that technical profile the aforementioned claim type signInNames.emailAddress is derived throught the partnerClaimType directive in the input claims of the technical profile:
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<!-- THIS IS THE LINE I AM REFERRING TO: -->
<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="extension_Salutation"/>
<PersistedClaim ClaimTypeReferenceId="givenName"/>
<PersistedClaim ClaimTypeReferenceId="surname"/>
<PersistedClaim ClaimTypeReferenceId="country"/>
<PersistedClaim ClaimTypeReferenceId="extension_Company"/>
<PersistedClaim ClaimTypeReferenceId="extension_Kundennummer"/>
</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>
So my question would be: where can I look to debug further? Am I looking in the right place or does the error message mean something completely different?
Also as a side note: due to the nature of the identity experience frameworks configuration, there is a lot of xml involved. Therefore I have posted the relying party policy file that also includes the relevant parts for the service connection as a gist here: https://gist.github.com/mmaedler/0a555fc3f9e6036e235a15419e7afdd5
UPDATE Additionally I have added all relevant (hopefully) Technical Profiles as well as the UserJourney itself to this new gist: https://gist.github.com/mmaedler/19ab309897f3a7d993816eb34adc7edb
Also I have used your advise to investigate further. Since I overwrite the Technical Profile LocalAccountSignUpMultiStep-1 to replace the current built in code verification with the one using our mailing backend it suggests the output of this is lacking the email address in a field/format that is expected by the following technical profile in step 2. I have added a new OutputClaim to LocalAccountSignUpMultiStep-2 with a ClaimTypeReferenceId="email" which lead to a new input appearing on registration step 2. Entering an email address there ended in a successful signup and token creation.
Misunderstanding, this XML snippet input claim translates to - "Please find me this user by searching for the email through all users' signInNames.emailAddress attribute".
The error from the log states that, when this lookup operation was performed, the value for signInNames.emailAddress was null. This means that the claim email was never output as an output claim in any previous technical profile called by an orchestration step.
Look back through all technical profiles called prior to this and determine which claim has the user email inside it. Make sure that claim is output by a technical profile that is called by an orchestration step.

How do i include email in the redirect to AZURE AD B2C

I have set up an Azure B2C tenant and used custom policies to add azure ad as an IDP so that users can sign up with their domain accounts. I can build a custom page where ask them for their email and then redirect them to the proper policy(one for work domain accounts and another for personal emails), so that they do not have to make the choice between work and personal emails. The problem is that I do not want to make the user enter the email once again. Is there a way/option to do this? I basically want to achieve something similar to what the common endpoint of Azure AD does for all accounts.
For a custom policy, if you add the "login_hint" query string parameter to the OpenID Connect authentication request, then you can default the login field to this login hint by adding the "DefaultValue" attribute to the "signInName" input claim for the "SelfAsserted-LocalAccountSignin-Email" technical profile as follows:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<DisplayName>Local Account Signin</DisplayName>
...
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" DefaultValue="{OIDC:LoginHint}" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
...
</OutputClaims>
...
</TechnicalProfile>
The "DefaultValue" attribute references a claims resolver that sets the "signInName" claim type to the "login_hint" parameter of the OpenID Connect authentication request.
See the Set up direct sign-in using Azure Active Directory B2C article for more information about passing the "login_hint" query string parameter.

How to populate a string collection from a REST API response

How can I populate a StringCollection from a REST API call in an Azure AD B2C custom policy?
My Rest API is returning this as its ResponseContent
class ResponseContent {
public string version;
public int status;
public string[] strings;
}
new ResponseContent
{
version = "1.0.0",
status = (int) HttpStatusCode.OK,
strings= new [] { "str1", "str2", "str3", "str4", "str5"}
},
The technical profile executes as expected but when it populates the StringCollection shows in the claims bag via the Journey Recorder as a list of strings
When I try and show these as a dropdown list in a self-asserted page the list is empty.
Is this possible to do this and if so how?
Here is my claim definition, as you can see no Enumeration
<ClaimType Id="strings">
<DisplayName>Strings to be populated from REST Service </DisplayName>
<DataType>stringCollection</DataType>
<AdminHelpText>blah.</AdminHelpText>
<UserHelpText>blah.</UserHelpText>
<UserInputType>DropdownSingleSelect</UserInputType>
</ClaimType>
<!-- here is the technical profile that i am using to populate the claims from it -->
</TechnicalProfile>
<TechnicalProfile Id="Populate-strings-from-app">
<DisplayName>Populate-Strings</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="AuthenticationType">None</Item>
<Item Key="ServiceUrl">XXXXXXXX</Item>
<Item Key="SendClaimsIn">QueryString</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" PartnerClaimType="client_id" DefaultValue="{OIDC:ClientId}" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="strings" />
</OutputClaims>
</TechnicalProfile>
In the claims bag I can see it is showing as a generic list which means I think i should be doing a transformation on it but i dont know if i have ever seen a dynamic transformation on a list without knowing what every element is
This is unfortunately not supported today :-( The only way to populate the enumeration is by providing a list of values in the policy.
I would recommend adding this as a suggestion on the Azure AD B2C feedback page for Azure AD (or vote for one if it already exists).

Resources