AppleAuthentication & Expo App Public Key

Apples docs aren’t very clear but you’re looking at “AppleID using OAuth” documentation which is only relevant when you use “Sign In With Apple” from a browser, not from a native app.

You’re doing everything right so far. Send the payload to your server as you’re doing. Then get the JWK (JSON Web Key) from this endpoint https://appleid.apple.com/auth/keys (probably best to cache the result for a while if you have high traffic, it really should never change, but it might).

Now you have everything you need to validate the original payload. No more REST calls required. Everything can be done locally from your server.

I’m not sure what you’re using on the server side as a platform but I was using PHP. I used this JWT package php-jwt/JWK.php at master · fproject/php-jwt · GitHub which seems to be a fork of google’s own JWT package GitHub - firebase/php-jwt: PHP package for JWT, with the added JWK class.

So, TL;DR; Use Apple’s JWK to validate your JWT payload on the server side using just those two piece of info. Useful google would be using a jwk to validate a jwt and include your language of choice.

My PHP Code is basically this.

	    // TODO: cache this
            $appleKey = file_get_contents('https://appleid.apple.com/auth/keys');

            $keys = JWK::parseKeySet($appleKey); // AIDOPK1
            
            if( count($keys) == 0 ) {
                TTLog::info('Invalid Apple Public Key');
                return false;
            }
            
	        $key = $keys[array_keys($keys)[0]];
            
            $jwt = $applePayload['identityToken'];

            $parts = explode(".", $jwt);

            if( count($parts) != 3 ) {
                TTLog::info('Invalid identityToken');
                return false;
            }

            $header = json_decode(base64_decode($parts[0]));
            TTLog::info("Header",$header); 
            
            $payload = json_decode(base64_decode($parts[1]));
            TTLog::info("Payload",$payload);

            try {
                $decoded = JWT::decode($jwt, $key, [$header->alg]);
                TTLog::info('Decoded',$decoded);
            } catch(Exception $e) {
                TTLog::info('Decode Exception',$e->getMessage());
                return false;
            }

            // Check to make sure aud matches either our own BundleID or Expo's
            if( !in_array($decoded->aud,Config::inst()->get(self::class,'validAuds')) ) {
                TTLog::info('Invalid AUD');
                return false;
            }
            
            if( ($decoded->sub == $payload->sub) && ($decoded->sub == $applePayload['user']) ) {
                // NOOP
            } else {
                TTLog::info('Invalid User/Sub');
                return false;
            }

            return true;

Josh

3 Likes