Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

New Apple Sign in keeps throwing Error HTTP 400 Invalid_grant

According to apple doc to validate the auth code against Apple we need to POST to http://appleid.apple.com/auth/token with this parameters:

#!java

token = generateJWT(keyId, teamId, clientId, certificatePath);

HttpResponse<String> response = Unirest.post(authUrl)
     .header("Content-Type", "application/x-www-form-urlencoded")
     .field("client_id", clientId)
     .field("client_secret", token)
     .field("grant_type", "authorization_code")
     .field("code", authorizationCode)
     .asString();

where:

  • authorization_code: Is the auth code provided by app client.

  • clientId: is provided by Apple

  • token: is Client Secret. A JWT generate with this code:

#!java

private static String generateJWT(String keyId, String teamId, String clientId, String certificatePath) throws Exception {
        if (pKey == null) {
            pKey = getPrivateKey(certificatePath);
        }

        return Jwts.builder()
                .setHeaderParam(JwsHeader.KEY_ID, keyId)
                .setIssuer(teamId)
                .setAudience("https://appleid.apple.com")
                .setSubject(clientId)
                .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 5)))
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .signWith(pKey, SignatureAlgorithm.ES256)
                .compact();
    }

private static PrivateKey getPrivateKey(String certificatePath) throws Exception {
        //read your key
        try (PEMParser pemParser = new PEMParser(new FileReader(certificatePath))) {
            final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
            final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
            final PrivateKey pKey = converter.getPrivateKey(object);
            return pKey;
        }
    }

We check that, the JWT, contains all the fields needed by apple:

#!json

{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "6876D87D6"
}
{
  "iat": 1578654031,
  "exp": 1578740431,
  "aud": "https://appleid.apple.com",
  "iss": "57675576576",
  "sub": "com.blahblah.client"
}

But this is the problem. It always return a 400 HTTP Error with this body:

#!json

{"error":"invalid_grant"}

From here we are completely lost. We do not understand why the code is not correct or why it has an invalid_grant error.

like image 228
Javier Sivianes Avatar asked Jan 15 '20 14:01

Javier Sivianes


2 Answers

In my case, front-end sent identityToken by mistake, instead of authorizationCode. That caused b'{"error":"invalid_grant"}'

like image 80
phuongtm Avatar answered Nov 05 '22 15:11

phuongtm


I was only able to get the invalid_grant error when sending requests with incorrect authorizationCode to https://appleid.apple.com/auth/token. Make sure you get the code the same way as in the swift code sample below.

I think this error is not about the client_secret. When I change the client_secret from a valid value to an empty string or some incorrect value, I just get the invalid_client error. And when both the authorizationCode and the client_secret are wrong, I also get the invalid_client error.

This is what is needed for the request:

  1. app_id is the bundle identifier for the app
  2. client_secret is the jwt token you create using the information shown in the question and sign with your private key you download from developer.apple.com also see my answer here

  3. grant_type is just the string "authorization_code"

  4. code is the authorizationCode you get from inside the app on a string format, not data format
data = {
    'client_id': app_id,
    'client_secret': client_secret,
    'grant_type': grant_type,
    'code': authorizationCode,
}

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
}

//python example request

response = requests.request(
    method='POST',
    url='https://appleid.apple.com/auth/token',
    data=data,
    headers=headers,
)

if response.status_code == 200:
    print("200")
else:
    print("error")
print(response.text)

The following swift code can be used to get the authorizationCode, identityToken and userIdentifier on the correct format.

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {

         let userIdentifier = appleIDCredential.user
         print("userIdentifier: \(userIdentifier)")

         let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8)!
         print("authorizationCode: \(authorizationCode)")

         let identityToken = String(data: appleIDCredential.identityToken!, encoding: .utf8)!
         print("identityToken: \(identityToken)")

    }
}
like image 32
DevB2F Avatar answered Nov 05 '22 16:11

DevB2F