Sign in with Apple Tutorial, Part 3: Backend – Token verification

The third part of a series Sign in with Apple. In this part, we will see how backend can use the token to sign up/sign in users.

If you are an iOS developer, you shouldn't need to read this part. Backend folk should do all the hassle setting up users' accounts or sign them in for you. Still, if you are a solo developer who does everything yourself or just curious about what your colleagues are doing, this article might benefit you.

The Basic #

Let's first talk about single sign-on flow. Most single sign-on flows are the same. It can sum up as follow:

Client

Users directed to provider, e.g., Facebook, Twitter, Apple Users grant/deny permissions that application requested Provider direct users back to the application along with token The application then uses this token to sign in (or create an account)

Backend

Use the token from the client to retrieve information from the provider, e.g., id, email Use that information sign in (or create an account if this is the first time)

That's it.

For Sign in with Apple, the backend steps are a little bit different but serve the same purpose. What you get after authenticate is JSON Web Tokens(JWT) . It contains most of the data you need in the payload part, so you don't need to make another request for that information. The only thing we need to do is verify that the payload wasn't changed along the way.

Sign in with Apple flow looks something like this:

Client

Users directed to Apple for Android and Web. Users presented with a sign-in dialog for iOS Users grant/deny permissions that application requested Apple directs users back to the application along with token. The delegate ( authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) ) get called with JWT ( .identityToken ) Application send this JWT to the application server

Backend

Verify JWT Use that information sign in (or create an account if this is the first time)

How to verify the token #

Before we use the token, we need to make sure that it was signed by Apple's private key. To do that, we need Apple's public key to verify the signature.

You can get the public key from the following endpoint:

GET https : / / appleid . apple . com / auth / keys

More information here Fetch Apple's public key for verifying token signature.

The response would look like this:

{

"keys" : [

{

"kty" : "RSA" ,

"kid" : "AIDOPK1" ,

"use" : "sig" ,

"alg" : "RS256" ,

"n" : "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w" ,

"e" : "AQAB"

}

]

}

This might look alienate to you. Believe it or not, you can generate public key from this information. From Apple's document, this weird JSON is JSON Web Key Set (JWKS) , and its contain everything you need for generating a public key.

If successful, the HTTP status code is 200 (OK) and the JWKSet.Keys object contains Apple's public key.

It quite difficult to write specific detail on how to get the public key from this JWKS, but every framework and language should have one or two libraries for this job. What you need to do is Google something like, JWKS to RSA in [programming language] and JWKS to public key in [programming language] and grab the most famous one in your language of choice.

Once you found your library, you can skip to next section, but if you are interested to know what are these weird gibberish mean, I can share what I found while Googling with you.

If you want to test whether your public key is correct, you can check with the one I already extracted https://gist.github.com/sarunw/d80490fea63a15b32000edd624b69a38. Warning

Don't use my key in any production app. It is a public key, but you never know it is mine or Apple.

What is JWKS #

JWKS stand for JSON Web Key (JWK) Set. It is a set of keys containing the public keys that use to verify JWT. In this case, it is a set of one key (Apple might add a new one in the future, so don't hard code it).

How to select the right key from the set #

If you decode JWT token that you got Apple (put it in https://jwt.io/), in the header you will see something like this:

{

"kid" : "AIDOPK1" ,

"alg" : "RS256"

}

kid is what you needed to identify the key.

From the spec

4.1.4. "kid" (Key ID) Header Parameter

The "kid" (key ID) Header Parameter is a hint indicating which key was used to secure the JWS. This parameter allows originators to explicitly signal a change of key to recipients. The structure of the "kid" value is unspecified. Its value MUST be a case-sensitive string. Use of this Header Parameter is OPTIONAL. When used with a JWK, the "kid" value is used to match a JWK "kid" parameter value.

You can see that both the JWT and JWKS got the same kid , AIDOPK1 .

Now that you know which key to use, the next question is how to get the public key from this JSON.

Retrieve key from JWK #

I do not fully understand everything in this section. If you have a math expert friend, I suggest you consult them :)

The public key of the RSA algorithm is made of the modulus ( n ) and the exponent ( e ) .

So, what we interest here are n and e value. We use these two numbers to generate a public key using any OpenSSL-backed libraries out there.

Some libraries might be able to pass string as-is for n and e , some might need an integer. To convert these string into an integer, you need to know what format it is represented.

n and e are Base64URL-encoded Big Endian byte array representations of numbers. .

As an example, I will show you how to decode e ( AQAB ) into a number.

Base64URL is Base64 with differs in the following:

Replaces "+" by "-" (minus)

Replaces "/" by "_" (underline)

Does not require a padding character

Forbids line separators

Here is a sample code of how to do it:

extension String {

static func base64urlToBase64 ( _ base64url : String ) - > String {

var base64 = base64url

. replacingOccurrences ( of : "-" , with : "+" )

. replacingOccurrences ( of : "_" , with : "/" )

if base64 . count % 4 != 0 {

base64 . append ( String ( repeating : "=" , count : 4 - base64 . count % 4 ) )

}

return base64

}

}

AQAB is the same in both Base64URL and Base64 format.

Base64 to decimal #

Each Base64 digit represents 6 bits of data. AQAB contains four digits (of 6 bits, a total of 24 bits) which can represent with three bytes (a total of 24 bits).

A Q A B 000000 010000 000000 000001

Convert to a three-byte array.

[00000001, 00000000, 00000001]

In Swift, you can convert it with following code:

let base64 = String . base64urlToBase64 ( "AQAB" )

let data = Data ( base64Encoded : base64 ) !

print ( data )

Output of the data will be the following:

po data

▿ 3 bytes

- count : 3

▿ pointer : 0x00007ffee8250e30

- pointerValue : 140732793163312

▿ bytes : 3 elements

- 0 : 1

- 1 : 0

- 2 : 1

People usually present this in the form of a hexadecimal string, since that aligns at the byte boundaries

three bytes array [00000001, 00000000, 00000001] Becomes [0000, 0001, 0000, 0000, 0000, 0001] (0x010001)

Then we can convert this to decimal.

(0 x 165) + (1 x 164) + (0 x 163) + (0 x 162) + (0 x 161) + (1 x 160) = 65537

To sum it up:

Base64 : AQAB

6 - bits array : [ 000000 , 010000 , 000000 , 000001 ]

Byte array : [ 00000001 , 00000000 , 00000001 ]

Hexadecimal : 01 00 01

Decimal : 65537

That's all. You use this number to generate a public key and use that key to verify the token.

After you verify that JWT hasn't tampered, its time to validate the data inside.

If you decode your JWT (You can copy your JWT token and decode it at https://jwt.io/), in payload section, you will see JSON with many fields.

{

"iss" : "https://appleid.apple.com" ,

"aud" : "com.sarunw.siwa" ,

"exp" : 1577943613 ,

"iat" : 1577943013 ,

"sub" : "xxx.yyy.zzz" ,

"nonce" : "nounce" ,

"c_hash" : "xxxx" ,

"email" : "xxxx@privaterelay.appleid.com" ,

"email_verified" : "true" ,

"is_private_email" : "true" ,

"auth_time" : 1577943013

}

For simple validation, you might need to validate these four fields.

Key Description Note iss (issuer) The issuer registered claim key. Should come from Apple ( https://appleid.apple.com in this case) aud (audience) The audience registered claim key. This is your app's bundle id in this case exp (expiration) The expiration time registered claim key. Apple set this to 10 minutes. iat (issued at) The issued at registered claim key, the value of which indicates the time at which the token was generated. You can check elapsed time since this issued time if you need custom expiration duration.

You use this information to validate that the token you received isn't too old, coming from Apple, and issued for your app.

Sign them in #

Now that you make sure the data is trustworthy let's see what information you can use to create an account (or sign them in).

Key Description Note sub (subject) The subject registered claim key, the value of which identifies the principal that is the subject of the JWT. This is a user id. You can use this to identify users. email User's email This can be their real email or a private one. Determine by is_private_email field email_verified A Boolean value that indicates whether the service has verified the email. The value of this claim is always true because the servers only return verified email addresses. Always true . is_private_email Determine whether email is Apple private one or not. In my test, is_private_email key will be absent instead of false if users use a real email.

There is no full name and realUserStatus information in JWT. If you want this information, you might need to send this along with the token. realUserStatus flag should act like a CAPTCHA anyway, so you might not need to send this along.

You can use sub to identify the existence of the account and create or sign them in accordingly.

This article covers only one scenario where you have an iOS app with an application server. If you want to implement Sign in with Apple in other platforms, there are extra steps you need to do to make it work. I might write about that in the future.

I think this simplest case should suffice for a simple app out there, and this article should give you a basic foundation that you can use for a more complex scenario.

I'm not a security expert, if you find any mistake in this article, please let me know.

Related Resources #

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

← Home