2
votes

Scenario: I'm using Angular 5 for front-end and .NET core 2.0 for back-end, MSAL.js to authenticate against Azure AD B2C in Angular SPA, then use returned id_token as Bearer Token to send requests to WebAPI endpoints.

I have successfully setup multi-tenant Azure AD as a provider in Azure AD B2C (followed the answer here Multi-tenant Azure AD in Azure AD B2C), but in the returned id_token, there's no claim for email address. Note: If I configure single-tenant Azure AD, I get back a claim with type http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress for email address, but wasn't able to do so with multi-tenant AD.

I believe the limitation is with Azure AD v2.0 that's been mentioned here: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-limitations

Question: How do I retrieve the user's email address after log in.

I followed the guideline in this article https://monteledwards.com/2017/10/18/a-complete-integration-azure-ad-b2c-azure-ad-graph-api-logic-apps/ to add an extra logic app to resolve email from id_token, but my problem is I don't have objectId back either.

Claims I've got back after successful authentication are:

iss - https://login.microsoftonline.com/<My-B2C-Tenant-Id>/v2.0/
exp - ticks
nbf - ticks
aud - My-B2C-App-Id
name - string
http://schemas.microsoft.com/identity/claims/identityprovider - tid
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - My-B2C-App-Id
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname - string
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname - string
nonce - GUID
http://schemas.microsoft.com/identity/claims/scope - User.Read
azp - GUID
ver - 1.0
iat - ticks

My technical profile for multi-tenant Azure AD -> Azure AD B2C is:

<TechnicalProfile Id="AzureADAccountProfile">
  <DisplayName>Log in with your work account</DisplayName>
  <Protocol Name="OpenIdConnect"/>
  <OutputTokenFormat>JWT</OutputTokenFormat>
  <Metadata>
    <Item Key="authorization_endpoint">https://login.microsoftonline.com/common/oauth2/v2.0/authorize</Item>
    <Item Key="client_id">My ID</Item>
    <Item Key="DiscoverMetadataByTokenIssuer">true</Item>
    <Item Key="HttpBinding">POST</Item>
    <Item Key="IdTokenAudience">My ID</Item>
    <Item Key="response_types">id_token</Item>
    <Item Key="scope">openid profile</Item>
    <Item Key="UsePolicyInRedirectUri">false</Item>
    <Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
  </Metadata>
  <CryptographicKeys>
    <Key Id="client_secret" StorageReferenceId="B2C_1A_AzureADSecret"/>
  </CryptographicKeys>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="enterpriseAuthentication" />
    <OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
    <OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="tid" />
    <OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="oid" />
    <OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
    <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="unique_name" />
    <OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
    <OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
    <OutputClaim ClaimTypeReferenceId="tenant" />
  </OutputClaims>
  <OutputClaimsTransformations>
    <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
    <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
    <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
  </OutputClaimsTransformations>
  <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
1

1 Answers

2
votes

Getting the email address that is associated with the user account differs between a consumer/personal account and an organization/work account.

Personal account

Reference: Azure Active Directory v2.0 tokens reference

The email address that is associated with the user account can be issued in the ID token.

1) Change the "scope" metadata item from "openid profile" to "openid profile email".

<Metadata>
  <Item Key="scope">openid profile email</Item>
</Metadata>

2) Change the "email" output claim from:

<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="unique_name" />

to:

<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />

Work account

The email address that is associated with the user account must be retrieved using the Microsoft Graph API.

1) Change the "AzureADAccountProfile" technical profile from "OpenIdConnect" to "OAuth2" and add the metadata items to retrieve the profile properties for the signed-in user.

Note: The "Get a user" operation doesn't return the tenant identifier of the signed-in user so the following technical profile creates the "identityProvider" claim, which is required for the alternative security identifier, from the domain part of the "userPrincipalName" property of this user.

<TechnicalProfile Id="AzureADAccountProfile">
  <DisplayName>Log in with your work account</DisplayName>
  <Protocol Name="OAuth2"/>
  <OutputTokenFormat>JWT</OutputTokenFormat>
  <Metadata>
    <Item Key="AccessTokenEndpoint">https://login.microsoftonline.com/organizations/oauth2/v2.0/token</Item>
    <Item Key="authorization_endpoint">https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize</Item>
    <Item Key="BearerTokenTransmissionMethod">AuthorizationHeader</Item>
    <Item Key="ClaimsEndpoint">https://graph.microsoft.com/v1.0/me</Item>
    <Item Key="client_id"><!-- Enter your client ID --></Item>
    <Item Key="DiscoverMetadataByTokenIssuer">true</Item>
    <Item Key="HttpBinding">POST</Item>
    <Item Key="IdTokenAudience"><!-- Enter your client ID --></Item>
    <Item Key="response_types">code</Item>
    <Item Key="scope">https://graph.microsoft.com/user.read</Item>
    <Item Key="UsePolicyInRedirectUri">false</Item>
    <Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
  </Metadata>
  <CryptographicKeys>
    <Key Id="client_secret" StorageReferenceId="B2C_1A_AzureADSecret"/>
  </CryptographicKeys>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="enterpriseAuthentication" />
    <OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="displayName" />
    <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="mail" />
    <OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="givenName" />
    <OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="surname" />
    <OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="id" />
    <OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="userPrincipalName" />
  </OutputClaims>
  <OutputClaimsTransformations>
    <OutputClaimsTransformation ReferenceId="CreateAzureADIdentityProvider" />
    <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
    <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
    <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
  </OutputClaimsTransformations>
  <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

2) Create the "CreateAzureADIdentityProvider" claims transformation.

<ClaimsTransformation Id="CreateAzureADIdentityProvider" TransformationMethod="ParseDomain">
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="userPrincipalName" TransformationClaimType="emailAddress" />
  </InputClaims>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="identityProvider" TransformationClaimType="domain" />
  </OutputClaims>
</ClaimsTransformation>