Sign in with Apple: A Zero Code Change Approach

Farasath Ahamed
Identity Beyond Borders
10 min readJun 10, 2019

I will be doing a webinar on how you can implement Sign in with Apple using a zero code integration approach on 10th July. If you are interested please feel free to register.

Apple announced a lot of cool stuff during its worldwide developer conference last week (WWDC 2019). Amidst ingenious innovations like the $999 monitor stand (pun intended!), there was one particular announcement that caught my eye. The introduction of the “Sign in with Apple” feature.

Image from https://www.macobserver.com/

In a nutshell “Sign With Apple”,

  • Provides an API for developers to build a login functionality.
  • Allows users with Apple IDs to use their existing account to sign at third-party apps
  • Control the information shared at login.

At a glance, this seems like just any other federated login option such as Login with Google, Facebook or any other Identity Provider. But Apple claims there’s more. According to the keynote, when a user logs in using the Sign in with Apple, they can choose either to share their real email or a randomly generated email address unique to each app. These randomly generated email addresses will act as a proxy between the app and the user.

Sign In with Apple: The Flow

Behind the scenes, sign in with Apple uses the OIDC Authorization code flow. As OpenID Connect is a standard, implementing/adopting the login is going to be a lot easier (With the availability of a lot of client libraries, etc.).

Simply it's a two-step process to complete the login

  1. Redirect the user to Apple’s authorize endpoint (think of it as the login initiation endpoint). Apple takes care of authenticating the user and send your app a “code”.
  2. Your app exchanges the “code” to a token containing the logged in user’s information.

(If you like to read more on the authorization code flow read my blog post.)

Sign In with Apple: Deep Dive

Let’s dive a bit deep and see the “Sign in with Apple” Flow in depth. As I explained in the previous section, the “Sign in with Apple” uses the OpenID Connect Authorization Code flow with slight differences.

We’ll look at the flow in deep and analyze where it deviates from the standard flow etc. in this section.

In order to complete “Sign in with Apple,” there are two steps

Step 1: Obtaining the authorization code

If you are familiar with the OIDC Authorization Code, this would typically mean redirecting the user to the authorization endpoint of Apple Sign in, authenticating the user (done by Apple) and getting back an intermediate code known as the “authorization code”.

I have sketched the flow below. Let me explain it a bit more

Obtaining an authorization code

When a user clicks on that “Sign in with Apple” in the app, your app will send a GET request to Apple will be in the form below.

https://appleid.apple.com/auth/authorize?response_type=code&redirect_uri=https%3A%2F%2Fidp.demologin.com%3A9443%2Fcommonauth&state=a98e0e95-2d21-49a5-aaca-2a50beced716%2COIDC&client_id=idp.demoglogin.com&scope=openid%20email

In the typical OIDC Authorization request, scope value MUST contain the “openid” string. The openid string signals the authorization server that the flow is “OpenID Connect” and not Pure OAuth (OAuth is meant for authorization not authentication).

But while playing around with Sign in with Apple, I noticed it is not the case at the moment. Even scope values such as

  • email
  • name email

seem to be accepted as valid ones. Due to the lack of documentation, we do not have a list of accepted scopes. But if you send a random scope value it seems to fail. Right now the authorization request of the Sign in with Apple is a bit of a trial and error scenario due to lack of docs.

Once you send the authorization request, Apple will authenticate you in several (two) steps. It is important to note that Apple strictly enforces two-factor authentication for Sign in with Apple. If you don’t have two-factor authentication setup for your Apple ID then no Sign in with Apple for you :D

  • Username and password
  • Second Factor: One-time password from a trusted device
  • Then Apple will be prompting you for consent to share your email with the app. Noticed that you only get this option (ie. to chose whether to share your email or not) when you log in to an app for the first time. Thereafter Apple simply prompts whether to allow the app to sign you in or not.
  • Once you complete the above steps, your app will receive a code and the state value. These two are very important.

The state plays two roles,

> Acts as a correlation ID for the app to correlate request and received response.

> Serves as a security mechanism to prevent CSRF attacks. Basically

The code, on the other hand, is an intermediate opaque string that is bound to the user’s authenticated session. However, your app cannot get any information on the authenticated user without authenticating itself. That’s the next step.

Step 2: Exchanging the authorization code for id_token

So your app now has a code sent back from Apple, but to actually get the information about the user your app needs to send a token request with the obtained code.

When sending the token request, your app has to authenticate itself to show that it is a valid one and the code was intended to be used by it. You do this typically in an OIDC Authorization code flow by sending a client_id and client_secret in the token request.

Check apple developer docs for parameters required by the token request.

The major difference I saw was in the client_secret parameter sent in the token request. In most identity providers the client_secret is a randomly generated string. But Apple wants the app to create its own secret, creating a signed JWT (a JSON Web Token) using its private key.

Basically, something in the form, where “iss” is your Apple Team ID and “sub” is your service id or the client_id

{
"alg": "ES256",
"kid": "ABC123DEFG"
}
{
"iss": "DEF123GHIJ",
"iat": 1437179036,
"exp": 1493298100,
"aud": "https://appleid.apple.com",
"sub": "com.mytest.app"
}

This is somewhat similar to the private_key_jwt client authentication mechanism defined by OpenID Connect Specification. However, Apple’s approach slightly differs from the private_key_jwt approach from the spec.

  • Apple’s client_secret does not require a jti. A unique identifier for the JWT token that usually prevents replay.
  • The authentication token (JWT) required by Apple, is sent as the client_secret parameter in the request. But the private_key_jwt method requires you to send the JWT as client_assertion parameter.

Token Response

  • Once you successfully complete the token request. You basically get a response similar to below.
{
“access_token”:”a645a412df1c....DHg”,
”refresh_token”:”rf3826e27d..rRa8dciiRSg”,
”id_token”:”eyJraWQiOiJBSURPUEsxIi......DI8GhKF81IGa4_kv4hw-7J2kLmKSJHUpY02oXNxEqklv-yjh_umleqAweMKVX2h21NSnS5v50tcJIJ8uX2GYl_neyEknJ0IgkfRbyrZBksw”,
”token_type”:”Bearer”,
”expires_in”:3600}

The one that is most important to your app is the “id_token”. Once you decode the id_token (which is a JWT). You should be able to see the user information as below. ‘sub’ is the identifier of the user sent by Apple.

{
“iss”: “https://appleid.apple.com",
“aud”: “idp.demoglogin.com”,
“exp”: 1560244232,
“iat”: 1560243632,
“sub”: “001126.d3c6971f4faa4ccd80027e3654fa404a.1616”,
“at_hash”: “ON8oFWz6RXpPXEU3XEZUsg”
}

Note: Although I allowed the option to share my email, I could not get it in the id_token sent by Apple. Will update the post if I manage to get it working. Could be that I am sending a wrong scope value.

User Info Endpoint

Most OpenID Connect Providers support the OIDC User Info endpoint. This endpoint usually supports obtaining user claims using the access token obtained with the OIDC authentication flows.

At the moment Apple does not seem to support this. We will have to wait and see whether this will be supported in the future.

What about Sign out?

Well, Sign in happened fine.

How do we sign out of the session? or does Sign in with Apple creates a session at all?

After playing around with the “Sign in with Apple”, I noticed that logging in with “Sign in with Apple” option does not create a session in the browser at the moment. (At least from what I have found so far)

Which means if you send two authorization requests to Apple from the same browser one after the other, you will have to login again and again :)

When I entered my username at the login page, I noticed that a request is sent as below that validated my account. The “isRememberMeEnabled=false” could be the reason for not maintaining a session.

https://appleid.apple.com/appleauth/auth/federate?isRememberMeEnabled=false

So every time your application sends you to log in, you will have to authenticate by going through all the steps (two steps). Therefore it is your app that will have to maintain a session and log the users out of the session.

So as for now, you don’t have to worry about “Sign in out” part.

What does this mean to you?

  • If you are an app developer, chances are that as soon as this feature gets commercial support you would have to start supporting it as a login option for your users.
  • Since Apple enforces multi-factor authentication and allows users to shield their private information from third-party apps, “Sign in with Apple” could become the defacto login mechanism for nearly 1.4 billion Apple ID accounts.
  • From a developers perspective, this means your app would need some refactoring/modification in order to support the “Sign In with Apple” option
  • But, If your app already supports OpenID Connect, then you would have less hassle in supporting this. But still requires some code changes to deal with providing multiple options for login and every time a “Sign with X” (X is an IDP just like Apple, Google) comes in.

So do we have other options to consider?

Supporting Sign in with Apple with Zero Code change

What I am going to describe is one of the possible methods to support “Signing with Apple” or any other “Sign in with X” option in a scalable manner.

In thing solution, we will be using an IAM provider to do the “Signing with Apple” part for us. Your app will only be talking the IAM provider. This essentially means we will be introducing a layer between your app and external Identity Providers.

I will be using the WSO2 Identity Server as an example in my solution. But you can use any IAM provider that supports OIDC Authentication with external identity providers to build the same solution.

Sign in with Apple using an IAM provider

Step 1: Make your app speak in a standard protocol

Connect your app to the IAM provider via a standard protocol. Most IAM providers support standard protocols such as OpenID Connect, SAML, CAS, etc. If your app already speaks in any of these protocols you have less to do.

If not then you can follow an approach such the one explained in my post here to achieve it without a code change. There are filters and other mechanisms you can follow to make your app to communicate in a standard protocol with an IAM provider without actually modifying the code much.

So basically this means your app now knows to talk to an Identity Provider in a standard protocol.

Step 2: Make your IAM provider do the “Sign in with Apple”

If your IAM provider supports standard OpenID connect federation, chances are that this is pretty easy to do. Typically this would involve three steps

  1. Configure your IAM (in my example WSO2 Identity Server) as a trusted entity in the Apple. Once you register you would get a client_id, client_secret from Apple and also register WSO2 Identity Server’s callback URI at Apple.
  2. Configure the obtained details in #1 and add Apple as a trusted Identity Provider in your IAM provider.
  3. Engage configured Identity provider(Apple) during your app’s authentication flow.

A detailed post on how we actually do this can be found here.

What’s next: Support another “Sign in with X”

Let’s say you wanted to support sign in with Google for the same app. No problemo! As you can see this where the introduced IAM layer comes into play.

It’s just a matter of following Step2 for Google instead of Apple. And you don’t even need to worry about touching the app.

WSO2 Identity Server, as well as most IAM providers, provide other capabilities such as attribute transformation and engaging rules in the authentication flow.

So having an IAM layer to communicate with the external identity providers helps you keep your app logic clean and free of authentication/authorization login that tends to change dynamically.

--

--