1
votes

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.

1
Discussion about the same problem is moved here: stackoverflow.com/questions/51837845/…markodj

1 Answers

1
votes

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.