"Sign In with Apple" Custom Policy for Azure AD B2C
Published Jul 23 2019 09:00 AM 9,536 Views
Iron Contributor

Disclaimer: Sign In with Apple is currently a preview feature. All things were working as described upon publishing of this article, but things may have changed by the time you read this article.

 

A month ago Apple held its annual Worldwide Developers Conference (WWDC) focusing on improvements in iOS and MacOS. I'm not doing a rehash of that event here, but there was one feature in particular that was interesting from an identity perspective; namely "Sign In with Apple". Billed as a privacy-focused alternative to signing in with Google and Facebook identities, Apple intends to make this a more popular option with "nothing" shared with the web pages that are embedding this. Well, that and signing into native apps where developers have to offer Apple signin if they offer third-party login options.

 

Apple has received some criticism for basing their efforts on OpenID Connect while not going all-in and make the implementation compliant:
https://bitbucket.org/openid/connect/src/default/How-Sign-in-with-Apple-differs-from-OpenID-Connect....

 

I'm not a fan of tweaking protocols like this, and prefer implementations to be compliant, but I'm hoping this is mostly a preview thing and that the final implementation will be up to spec.

 

The reception around the internet seems to be positive from the user side of things. (Not that the average user is all that concerned, but for those who are concerned enough to have an opinion that seems to be the general gist.) I don't disagree with this, but in my opinion the difference between Apple and the alternatives is not so much a technical feat as it is about policies. An identity provider can be very restrictive with what info it gives out, but there's no doubt some of them will provide a lot of data as long as the user consents to it.

 

One of the first things I asked myself was "can I use this with Azure AD B2C"? And after some initial lab work the answer was "yes, you can" when Microsoft provided clear instructions on how to do this with built-in policies:
https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-apple

 

Since Apple doesn't follow the protocol there are some tweaks you need to perform, but following the steps above you should be able to make it work. (To be fair - Apple never said they followed a standard either.) Do note that if you're testing in the Azure Portal you need to remove &prompt=login from the URL for it to work or you will just get a generic "Bad request".

 

While I love built-in policies as much as the next guy when doing exploratory testing, it often falls short in the real world use cases so the bigger question for me is how to make it work with custom policies.

 

I am basing myself on previous lab work and the following code sample:
https://github.com/ahelland/Identity-CodeSamples-v2/tree/master/aad-b2c-custom_policies-dotnet-core

 

A walkthrough of the stuff I'm adding this into (skippable if you're familiar with custom policies):
https://techcommunity.microsoft.com/t5/Azure-Developer-Community-Blog/Advanced-Home-Realm-Discovery-...

 

To make this work we will add to the "HRD_Internal" custom policy.

 

First you need to add a "Claims Provider" in 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="METADATA">%apple-metadata-endpoint%</Item>
            <Item Key="response_types">code</Item>
            <Item Key="scope">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>
            <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="login_hint" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
            <OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
            <OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="sub" />
            <OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="sub" />
            <OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
            <OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
            <OutputClaim ClaimTypeReferenceId="email" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
            <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
            <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
            <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
          </OutputClaimsTransformations>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

 

And then we add a new Orchestration Step to the "HRD_Internal" User Journey:

 

        <OrchestrationStep Order="7" Type="ClaimsExchange" ContentDefinitionReferenceId="api.signuporsignin">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
              <Value>idp</Value>
              <Value>apple</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="HintedAppleExchange" TechnicalProfileReferenceId="AppleID" />
          </ClaimsExchanges>
        </OrchestrationStep>

 

Make sure that you align the step order accordingly.

 

I have previously used an Azure Function to do the home realm discovery so I'm continuing this pattern and adding a new option:

 

//For AppleID accounts
if (email == "tim@apple.com")
    {

log.Info($"Identity Provider: Apple");
        return request.CreateResponse<ResponseContent>(
            HttpStatusCode.OK,
            new ResponseContent
            {
version = "1.0.0",
                status = (int) HttpStatusCode.OK,
                userMessage = $"Your account seems to be an Apple account.",
                idp = "apple",
                signInName = email
            },
new JsonMediaTypeFormatter(),
            "application/json");
    }

 

(The beautiful hardcoding approach still in play here.)

 

So in this case the first step looks like this:

Apple_IdP_01.png

 

Which will take you to Apple's sign in screen:

Apple_IdP_02.png

 

You will notice that Apple doesn't care about the email you entered as a login_hint - this is purely to drive the B2C logic here.

 

I have previously consented, so if you haven't things will look slightly different:

Apple_IdP_03.png

When signing up you will be prompted to enter your name manually (a guid pre-filled for display name, but this can be changed):

Apple_IdP_04.png

 

And then you will be redirected back to your test page:

Apple_IdP_05.png

So, things basically work. Sort of…

 

The user account is created with a pseudo-random guid as the "name" attribute. (Courtesy of Apple's algorithms.) In this case the "user name" is tim@apple.com (as we used in our hardcoded Azure Function above).

 

Apple doesn't include the email in the claims, so you don't get that back from them as you do with most other providers. (We did a mapping in the policy to get around this.) It will be possible to generate site-unique guid-based email addresses, but that doesn't seem to work as expected in the Apple setup yet. This also means that some other things might break…

 

The approach I used before for doing the home realm discovery passed along the view to a screen where you can choose between different identity providers when you enter an unknown email address. This breaks when returning to B2C (the Apple SignIn itself works). The reason is that we assume email will be available for all providers (<Item Key="setting.operatingMode">Email</Item>), and our Apple option doesn't go along with this.

 

For most purposes I don't really care if you provide your real email address or not when signing up as long as I'm doing basic authentication things. If I'm doing authorization I might have some issues with totally anonymous identities, but that's a bigger discussion. (I'm not saying bill.gates@gmail.com is more credible for that purpose.) Real or not I do prefer it to be an actual email address for the rest of the flows to work, but I'm not putting a lot of effort into this before knowing what the final bits from Apple will be like.

 

I've updated the code on GitHub, but if you don't want to setup Apple credentials yet (it requires an Apple Developer account) you can provide a fake metadata address and a fake client secret as the value of the B2C_1A_AppleIDAppSecret key.

 

If Apple's implementation remains as it is now I'm not ready to roll out the Azure AD B2C support for it, but with a bit of luck things will change to the better before September and we can improve upon the B2C bits of it accordingly.

1 Comment
Version history
Last update:
‎Jul 23 2019 08:20 AM
Updated by: