Sign in with apple - Multiple public keys returned

This afternoon we noticed that the GET https://appleid.apple.com/auth/keys endpoint is returning multiple public keys (3) now and the one that was working before (kid: "AIDOPK1") is no longer working for decrypting the id token returned from POST https://appleid.apple.com/auth/token and has broken our implementation. We wanted to know which public key we should use moving forward, as it seems the second one returned in the list of public keys is working, but we don't know whether that will be subject to change. Please let us know what to do.


Thanks!

  • I made a loop, but my kid does not correspond to the kids returned by apple, what should I do? thanks

Add a Comment

Replies

Same thing... This is so frustrating!


Or do you try to verify using every single key in a loop?

I also noticed that the endpoint started to return 3 public keys. I originally had the code to check the first key it finds in the array. That obviously broke Sign in with Apple on our app. I just switched the code to check each key to see if it will successfully verify the ID Token signature. Seems kind of silly that we have to check each key, but Apple hasn't provided documentation on how to handle multiple public keys (what are the other ones even for?).

Apple send back the Key Id "kid" in the response so you can lookup the appropriate key

The identityToken of ASAuthorizationAppleIDCredential contains in the header the "kid" you need to match with the list I guess.

Same thing. That broke Sign in with Apple on our app too.

I'm now just checking all three tokens looking for a match. Didn't know about "kid", maybe will replace with it later.

Get the ASAuthorizationAppleIDCredential from the delegate

// ASAuthorizationControllerDelegate
publicfunc authorizationController(controller: ASAuthorizationController, 
                                   didCompleteWithAuthorization authorization: ASAuthorization){
...
}

Get the String encoded JWT from the credential:

String(data: credential.identityToken, encoding: .utf8)


Go to https://jwt.io and decode the JWT

// Header
{
  "kid": "eXaunmL",
  "alg": "RS256"
}
// Payload
{
  "iss": "https://appleid.apple.com",
  "aud": "***",
  "exp": 1581598403,
  "iat": 1581597803,
  "sub": "***",
  "c_hash": "***",
  "email": "***",
  "email_verified": "true",
  "auth_time": 1581597803
}


In the header you can see the "kid".


This kid will match with one of the kid from https://appleid.apple.com/auth/keys

{
  "keys": [
  {...},
  {
  "kty": "RSA",
  "kid": "eXaunmL",
  "use": "sig",
  "alg": "RS256",
  "n": ...,
  "e": "AQAB"
  },
  {...}
]
}


At some point you are sending the identityToken to your backend. Your backend has to check the header to get the correct kid to get the correct key from the auth/keys. So you have to fix the issue on the backend.

Thanks for the tip. I found the documentation for kid in the RFC spec: https://tools.ietf.org/html/rfc7515#section-4.1.4.
Swapped out my implementation to check the id_token JWT for a kid claim and used that value to match against the public keys in Apple's endpoint.

I loop through the public keys and it works!

I'm using SwiftJWT https://github.com/IBM-Swift/Swift-JWT.git and it provides a method:

Code Block
        let jwtDecoder = JWTDecoder(keyIDToVerifier: getVerifier)

which you pass a kid and it returns a verifier.

Code Block
    private func getVerifier(using kid: String) -> JWTVerifier?  {
        guard let publicKeyPEM = toPEM(kid: kid) else {
            return nil
        }
        guard let publicKeyData = publicKeyPEM.data(using: .utf8) else {
            return nil
        }
        return JWTVerifier.rs256(publicKey: publicKeyData)
    }


toPEM(kid: kid) is just my method to lookup a key given a specific kid string.