Post-GA Revisit of "Sign In with Apple" for Azure AD B2C
Published Dec 31 2019 07:00 AM 11.1K Views
Iron Contributor

A couple of months back I did a little walkthrough of "Sign in with Apple" in an Azure AD B2C context, it being in a new preview and all:

https://techcommunity.microsoft.com/t5/azure-developer-community-blog/quot-sign-in-with-apple-quot-c...

 

But I didn't really follow up on that when it went GA back in October.

 

So, let's revisit this one.

 

I will assume you have performed the setup in the Apple Developer Portal as described here (follow along to you get to the "Creating the OIDC metadata endpoint" which will not be neccessary):

https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-apple

 

We were able to make it work in general so no complaints there, but there were some minor things with the setup that could be improved upon.

 

Apple didn't really conform to the OpenID Connect specs during the preview - yes, they were fairly similar but not quite there. While Apple still has a different implementation (like not providing a metadata endpoint) they have fixed most things.

 

What we did to get around the missing metadata was to provide an Azure Function that acted as an endpoint, but we can move this inside the policy instead (coding the necessary values into the xml). Let's update the claims provider:

B2C_1A_TrustFrameworkExtensions_Dev.xml

 

<ClaimsProvider>      
  <Domain>Apple</Domain>
  <DisplayName>Apple</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="AppleID">
      <DisplayName>Sign in with Apple</DisplayName>
      <Protocol Name="OpenIdConnect" />
      <Metadata>
        <Item Key="client_id">%apple-client-id%</Item>
        <Item Key="UsePolicyInRedirectUri">0</Item>
        <Item Key="authorization_endpoint"><a href="<a href="https://appleid.apple.com/auth/authorize</Item" target="_blank">https://appleid.apple.com/auth/authorize</Item</a>" target="_blank"><a href="https://appleid.apple.com/auth/authorize</Item</a" target="_blank">https://appleid.apple.com/auth/authorize</Item</a</a>>>
        <Item Key="AccessTokenEndpoint"><a href="<a href="https://appleid.apple.com/auth/token</Item" target="_blank">https://appleid.apple.com/auth/token</Item</a>" target="_blank"><a href="https://appleid.apple.com/auth/token</Item</a" target="_blank">https://appleid.apple.com/auth/token</Item</a</a>>>
        <Item Key="JWKS"><a href="<a href="https://appleid.apple.com/auth/keys</Item" target="_blank">https://appleid.apple.com/auth/keys</Item</a>" target="_blank"><a href="https://appleid.apple.com/auth/keys</Item</a" target="_blank">https://appleid.apple.com/auth/keys</Item</a</a>>>
        <Item Key="issuer"><a href="<a href="https://appleid.apple.com</Item" target="_blank">https://appleid.apple.com</Item</a>" target="_blank"><a href="https://appleid.apple.com</Item</a" target="_blank">https://appleid.apple.com</Item</a</a>>>
        <Item Key="IdTokenAudience">%apple-client-id%</Item>
        <Item Key="response_types">code</Item>
        <Item Key="scope">name email</Item>
        <Item Key="response_mode">form_post</Item>
        <Item Key="HttpBinding">POST</Item>
      </Metadata>
      <CryptographicKeys>
        <Key Id="client_secret" StorageReferenceId="B2C_1A_AppleIDAppSecret" />
      </CryptographicKeys>
      <InputClaims />
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
        <OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
        <OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="sub" />
        <OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="sub" />
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="email" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
        <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
        <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
        <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
      </OutputClaimsTransformations>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" />
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

 

 

 

 

This actually fixes one of the other quirks we saw at the same time too. We were not able to extract the email address of the user even if they consented, but this can now be retrieved.

 

We're still not able to extract the name directly from Apple's token - that seems to be a limitation in B2C at the moment as the name is in the following format according to Apple:

{

  "name": ​

    {

      "firstName": string,

      "lastName": string

    }, ​

    "email": string ​

}

 

And that doesn't seem to be what AAD B2C expects so there's a mismatch. (I've tried getting around this in various ways, but haven't succeeded yet. If you know how let me know in the comments.)

 

You should still request both name and email in the scope for future proofing though as Apple doesn't seem to support changing this after the initial consent.

 

For the sake of simplicity we're not embedding this into any existing journeys like I have done before so add something like this to have a dediated Apple user journey:

B2C_1A_TrustFrameworkExtensions_Dev.xml

 

 

 

<UserJourney Id="SuSiApple">
  <OrchestrationSteps>
    <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
      <ClaimsProviderSelections>
        <ClaimsProviderSelection TargetClaimsExchangeId="AppleExchange" />
      </ClaimsProviderSelections>
    </OrchestrationStep>
    <OrchestrationStep Order="2" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="AppleExchange" TechnicalProfileReferenceId="AppleID" />
      </ClaimsExchanges>
    </OrchestrationStep>        
    <OrchestrationStep Order="3" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="4" 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>
    <OrchestrationStep Order="5" 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="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
       
  </OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>

 

 

 

And to top it off add a new RP:

B2C_1A_SignUp_SignIn_Apple.xml

 

 

 

<TrustFrameworkPolicy 
xmlns:xsi="<a href="<a href="http://www.w3.org/2001/XMLSchema-instance" target="_blank">http://www.w3.org/2001/XMLSchema-instance</a>" target="_blank"><a href="http://www.w3.org/2001/XMLSchema-instance</a" target="_blank">http://www.w3.org/2001/XMLSchema-instance</a</a>>" 
xmlns:xsd="<a href="<a href="http://www.w3.org/2001/XMLSchema" target="_blank">http://www.w3.org/2001/XMLSchema</a>" target="_blank"><a href="http://www.w3.org/2001/XMLSchema</a" target="_blank">http://www.w3.org/2001/XMLSchema</a</a>>" 
xmlns="<a href="<a href="http://schemas.microsoft.com/online/cpim/schemas/2013/06" target="_blank">http://schemas.microsoft.com/online/cpim/schemas/2013/06</a>" target="_blank"><a href="http://schemas.microsoft.com/online/cpim/schemas/2013/06</a" target="_blank">http://schemas.microsoft.com/online/cpim/schemas/2013/06</a</a>>" 
PolicySchemaVersion="0.3.0.0" 
TenantId="yortenant.onmicrosoft.com" 
PolicyId="B2C_1A_Signup_Signin_Apple" 
PublicPolicyUri="<a href="<a href="http://yourtenant.onmicrosoft.com/B2C_1A_Signup_Signin_Apple" target="_blank">http://yourtenant.onmicrosoft.com/B2C_1A_Signup_Signin_Apple</a>" target="_blank"><a href="http://yourtenant.onmicrosoft.com/B2C_1A_Signup_Signin_Apple</a" target="_blank">http://yourtenant.onmicrosoft.com/B2C_1A_Signup_Signin_Apple</a</a>>" 
TenantObjectId="tenant_id">
  <BasePolicy>
    <TenantId>yourtenant.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkExtensions_Dev</PolicyId>
  </BasePolicy>
  <RelyingParty>
    <DefaultUserJourney ReferenceId="SuSiApple" />
    <UserJourneyBehaviors>
      <ContentDefinitionParameters>
        <Parameter Name="ui_locales">{Culture:RFC5646}</Parameter>
      </ContentDefinitionParameters>
      <ScriptExecution>Allow</ScriptExecution>
    </UserJourneyBehaviors>
    <TechnicalProfile Id="PolicyProfile">
      <DisplayName>PolicyProfile</DisplayName>
      <Protocol Name="OpenIdConnect" />
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="displayName" />
        <OutputClaim ClaimTypeReferenceId="givenName" />
        <OutputClaim ClaimTypeReferenceId="surname" />
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
        <OutputClaim ClaimTypeReferenceId="identityProvider" />        
      </OutputClaims>
      <SubjectNamingInfo ClaimType="sub" />
    </TechnicalProfile>
  </RelyingParty>
</TrustFrameworkPolicy>

 

 

That should give you a fairly working sample of Apple in your app. (You might want to go over additional claims needed an such.)

 

The beauty now that the experience is GA is that Apple have (unsurprisingly) adapted the UX to the device you are using. For instance it will work in Chrome on a Windows PC - you'll receive a code on your phone/pad/watch that you need to manually type in. But if you for instance do it on an iPhone with FaceID you just give the device an approving look and you're in - that's about as smooth as it gets:

 

SignIn on iPhone with FaceIdSignIn on iPhone with FaceId

 

Will this appeal to everyone? No, but if you are all in on the iOS ecosystem it's not a bad SignIn flow.

 

If you want to test this in an app there's a known-good sample here:

https://github.com/ahelland/Identity-CodeSamples-v2/tree/master/aad-b2c-custom_policies-dotnet-core

 

(Custom Policies not added to repo while publishing this post, but hoping to get that fixed soon.)

 

And if you don't want to mess around with the code you can just create a container-based Azure App Service and pull in the Docker images I've built (policies uploaded separately, and config still needed for web app):

https://hub.docker.com/r/ahelland/aad-b2c-custom_policies-dotnet-core-linux

Version history
Last update:
‎Dec 31 2019 06:54 AM
Updated by: