2

I have Azure B2C configured with custom policies to allow signups and sign ins of local accounts and multi-tenant Azure AD. The UI is Angular using MSAL with a .NET Core Web API backend. The custom policy defines a custom claim named clientIds that is populated through a REST call to an internally developed Azure Function.

This custom claim is successfully included in the id_token when the user signs in. However, it is NOT included in the access_token sent to the API. How do I include it in the access token?

Other details:

  • I found one post that said the solution was to add it as a PersistedClaim to the SM-AAD technical profile, but it didn't have an effect.

  • If I configure the custom claim in a user flow and an API Connector, everything works great - it's included in both tokens. I've studied the XML of the user flow to look for clues but didn't get anywhere. I cannot use user flows because I must support multi-tenant Azure AD.

  • I've included the claim in SignUpOrSignin.xml:

<RelyingParty>
  <DefaultUserJourney ReferenceId="AegisSignUpOrSignIn" />
  <Endpoints>
    <Endpoint Id="Token" UserJourneyReferenceId="RedeemRefreshToken" />
  </Endpoints>
  <TechnicalProfile Id="PolicyProfile">
    <DisplayName>PolicyProfile</DisplayName>
    <Protocol Name="OpenIdConnect" />
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="displayName" />
      <OutputClaim ClaimTypeReferenceId="email" />
      <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
      <OutputClaim ClaimTypeReferenceId="identityProvider" />
      <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
      <OutputClaim ClaimTypeReferenceId="otherMails" PartnerClaimType="emails" />
      <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
      <OutputClaim ClaimTypeReferenceId="clientIds" />
    </OutputClaims>
    <SubjectNamingInfo ClaimType="sub" />
  </TechnicalProfile>
</RelyingParty>
  • I think the solution is editing something in TrustFrameworkExtensions.xml but I've been trying all sorts of things for a couple days with no success. Here it is:
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" 
  PolicySchemaVersion="0.3.0.0" 
  TenantId="aegispremierdev.onmicrosoft.com" 
  PolicyId="B2C_1A_TrustFrameworkExtensions" 
  PublicPolicyUri="http://aegispremierdev.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
  
  <BasePolicy>
    <TenantId>aegispremierdev.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkLocalization</PolicyId>
  </BasePolicy>
  <BuildingBlocks>
    <ClaimsSchema>
      <ClaimType Id="prompt">
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="isForgotPassword">
        <DisplayName>isForgotPassword</DisplayName>
        <DataType>boolean</DataType>
        <AdminHelpText>Whether the user has selected Forgot your Password</AdminHelpText>
      </ClaimType>
      <ClaimType Id="clientIds">
        <DisplayName>Comma-separated list of authorized CRM clients</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
    </ClaimsSchema>
  </BuildingBlocks>

  <ClaimsProviders>

    <ClaimsProvider>
      <Domain>commonaad</Domain>
      <DisplayName>Sign in with Microsoft</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="AADCommon-OpenIdConnect">
          <DisplayName>Sign in with Microsoft</DisplayName>
          <Description>Login with your company 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">64e56953-cb54-48ee-ae81-3bd1325050d6</Item>
            <Item Key="response_types">code</Item>
            <Item Key="scope">openid profile email</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_AzureAdAppSecret"/>
          </CryptographicKeys>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="prompt" PartnerClaimType="prompt" DefaultValue= "select_account"/>
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
            <OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="azureAdIdpAuthentication" AlwaysUseDefaultValue="true" />
            <OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
            <OutputClaim ClaimTypeReferenceId="othermails" />
            <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email"/>
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
            <!-- <OutputClaim ClaimTypeReferenceId="clientIds" /> Adding it here doesn't help -->
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
            <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
            <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
            <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
          </OutputClaimsTransformations>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin"/>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Local Account SignIn</DisplayName>
      <TechnicalProfiles>
         <TechnicalProfile Id="login-NonInteractive">
          <Metadata>
            <Item Key="client_id">6a178567-c2fb-4f88-93e8-ed5775869485</Item>
            <Item Key="IdTokenAudience">3290010d-2d76-4fc0-95fc-6c6ad6c637bb</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="6a178567-c2fb-4f88-93e8-ed5775869485" />
            <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="3290010d-2d76-4fc0-95fc-6c6ad6c637bb" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" AlwaysUseDefaultValue="true" />
            <!-- <OutputClaim ClaimTypeReferenceId="clientIds" /> Adding it here doesn't help -->
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Local Account</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="REST-OnAegisAzureB2CSignUp" />
          </ValidationTechnicalProfiles>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

<!-- Adding it as a PersistedClaim as suggested in the referenced SO post doesn't help -->
<!--    <ClaimsProvider>
      <DisplayName>Session Management</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="SM-AAD">
          <DisplayName>Session Mananagement Provider</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.DefaultSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <PersistedClaims>
            <PersistedClaim ClaimTypeReferenceId="objectId" />
            <PersistedClaim ClaimTypeReferenceId="signInName" />
            <PersistedClaim ClaimTypeReferenceId="authenticationSource" />
            <PersistedClaim ClaimTypeReferenceId="identityProvider" />
            <PersistedClaim ClaimTypeReferenceId="newUser" />
            <PersistedClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" />
            <PersistedClaim ClaimTypeReferenceId="clientIds" />
          </PersistedClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectIdFromSession" DefaultValue="true" />
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>-->
    
    <ClaimsProvider>
      <DisplayName>Self Asserted</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="SelfAsserted-Social">
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="email" />
            <InputClaim ClaimTypeReferenceId="authenticationSource" />
          </InputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="REST-OnAegisAzureB2CSignUp"/>
          </ValidationTechnicalProfiles>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>REST APIs - OnAegisAzureB2CSignUp</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="REST-OnAegisAzureB2CSignUp">
          <DisplayName>Verify user exists in DonorOne Users table</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <!-- Set the ServiceUrl with your own REST API endpoint -->
            <Item Key="ServiceUrl">https://apt-fa-azure-b2c-dev-wus2.azurewebsites.net/api/onAegisAzureB2CSignUp</Item>
            <Item Key="SendClaimsIn">Body</Item>
            <Item Key="AuthenticationType">Basic</Item>
            <Item Key="AllowInsecureAuthInProduction">false</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_RestApiUsername" />
            <Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_RestApiPassword" />
          </CryptographicKeys>
          <InputClaims>
            <!-- Claims sent to your REST API -->
            <InputClaim ClaimTypeReferenceId="email" />
            <InputClaim ClaimTypeReferenceId="authenticationSource" />
          </InputClaims>
          <OutputClaims>
            <!-- Claims parsed from your REST API -->
            <OutputClaim ClaimTypeReferenceId="clientIds" />
          </OutputClaims>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>REST APIs - OnAegisAzureB2CSignIn</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="REST-OnAegisAzureB2CSignIn">
          <DisplayName>Get user extended profile Azure Function web hook</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://apt-fa-azure-b2c-dev-wus2.azurewebsites.net/api/onAegisAzureB2CSignIn</Item>
            <Item Key="SendClaimsIn">Body</Item>
            <Item Key="AuthenticationType">Basic</Item>
            <Item Key="AllowInsecureAuthInProduction">false</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_RestApiUsername" />
            <Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_RestApiPassword" />
          </CryptographicKeys>
          <InputClaims>
            <!-- Claims sent to your REST API -->
            <InputClaim ClaimTypeReferenceId="email" />
            <InputClaim ClaimTypeReferenceId="signInName" />
            <InputClaim ClaimTypeReferenceId="authenticationSource" />
          </InputClaims>
          <OutputClaims>
            <!-- Claims parsed from your REST API -->
            <OutputClaim ClaimTypeReferenceId="clientIds" />
          </OutputClaims>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Local Account</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="ForgotPassword">
          <DisplayName>Forgot your password?</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="isForgotPassword" DefaultValue="true" AlwaysUseDefaultValue="true"/>
          </OutputClaims>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>
        <TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
          <Metadata>
            <Item Key="setting.forgotPasswordLinkOverride">ForgotPasswordExchange</Item>
          </Metadata>
        </TechnicalProfile>
        <TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

  </ClaimsProviders>

  <UserJourneys>

    <UserJourney Id="AegisSignUpOrSignIn">
      <OrchestrationSteps>

        <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
          <ClaimsProviderSelections>
            <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
            <ClaimsProviderSelection TargetClaimsExchangeId="AzureADCommonExchange" />
            <ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
          </ClaimsProviderSelections>
          <ClaimsExchanges>
            <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- Check if the user has selected to sign in using one of the social providers -->
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
            <ClaimsExchange Id="AzureADCommonExchange" TechnicalProfileReferenceId="AADCommon-OpenIdConnect" />
            <ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="3" Type="InvokeSubJourney">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
              <Value>isForgotPassword</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <JourneyList>
            <Candidate SubJourneyReferenceId="PasswordReset" />
          </JourneyList>
        </OrchestrationStep>

        <!-- For social IDP authentication, attempt to find the user account in the directory. -->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>authenticationSource</Value>
              <Value>localAccountAuthentication</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId). 
          This can only happen when authentication happened using a social IDP. If local account was created or authentication done
          using ESTS in step 2, then an user account must exist in the directory by this time. -->
        <OrchestrationStep Order="5" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent 
          in the token. -->
        <OrchestrationStep Order="6" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>authenticationSource</Value>
              <Value>azureAdIdpAuthentication</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect 
             from the user. So, in that case, create the user in the directory if one does not already exist 
             (verified using objectId which would be set from the last step if account was created in the directory. -->
        <OrchestrationStep Order="7" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="8" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="RESTOnAegisAzureB2CSignIn" TechnicalProfileReferenceId="REST-OnAegisAzureB2CSignIn" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="9" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />

      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb" />
    </UserJourney>
    
    </UserJourneys>

  <SubJourneys>
    <SubJourney Id="PasswordReset" Type="Call">
      <OrchestrationSteps>
        <!-- Validate user's email address. -->
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- Collect and persist a new password. -->
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
      </OrchestrationSteps>
    </SubJourney>
  </SubJourneys>

</TrustFrameworkPolicy>
Roger
  • 2,118
  • 1
  • 20
  • 25
  • Is the issue when you redeem your refresh token for a new access token? Or the initial access token does not have the same claims as the initial id token? – Jas Suri - MSFT Jan 05 '23 at 14:51
  • @JasSuri-MSFT I think it's the 2nd thing. Looking at the network calls that MSAL does when a user first logs in, there are two requests to the `oauth2/v2.0/token` endpoint. The first has `grant_type=authorization_code` and the response includes an id_token that contains the custom claim and a refresh_token, but no access_token. So MSAL makes a 2nd request with `grant_type=refresh_token`. That one returns an id_token and an access_token, but NEITHER includes the custom claim. – Roger Jan 05 '23 at 16:17
  • 1
    Your refresh token flow won’t return the claim unless in your refresh token user journey (`RedeemRefreshToken`) calls your api technical profile again. Note, there might be some bug if that claim is a string array in custom refresh token journey. – Jas Suri - MSFT Jan 06 '23 at 10:26

1 Answers1

2

@JasSuri-MSFT that was the trick - thx! This was the UserJourney I added to TrustFrameworkExtensions.xml:

<UserJourney Id="AegisRedeemRefreshToken">
  <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
  <OrchestrationSteps>
    <OrchestrationStep Order="1" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="RefreshTokenSetupExchange" TechnicalProfileReferenceId="RefreshTokenReadAndSetup" />
      </ClaimsExchanges>
    </OrchestrationStep>
    
    <OrchestrationStep Order="2" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="CheckRefreshTokenDateFromAadExchange" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId-CheckRefreshTokenDate" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- I inserted step 3, which calls the REST API to return the clientIds claim -->
    <OrchestrationStep Order="3" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="RESTOnAegisAzureB2CSignIn" TechnicalProfileReferenceId="REST-OnAegisAzureB2CSignIn" />
      </ClaimsExchanges>
    </OrchestrationStep>
    
    <OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
  </OrchestrationSteps>
</UserJourney>

Then I referenced this journey from SignUpOrSignin.xml:

<Endpoint Id="Token" UserJourneyReferenceId="AegisRedeemRefreshToken" />

My REST API expects two claims to be sent in: emailAddress and authenticationSource. They were not included after the above changes, so I attempted to include them by overriding a couple technical profiles. That is, I added this to the ClaimsProviders section in TrustFrameworkExtensions.xml:

<ClaimsProvider>
  <DisplayName>Refresh token journey</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="RefreshTokenReadAndSetup">
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" />
      </OutputClaims>
    </TechnicalProfile>
    <TechnicalProfile Id="AAD-UserReadUsingObjectId-CheckRefreshTokenDate">
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" />
      </OutputClaims>
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

That worked to include the email address, but for reasons I don't yet understand the authenticationSource is still not passed in. But that's out of scope for this question, so I'll end it here.

Roger
  • 2,118
  • 1
  • 20
  • 25