Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

invalid_client for sign in with apple

What I try to achieve:

  • iOS client sends a JWT token to the backend.
  • Backend (Java) calls https://appleid.apple.com/auth/token to verify the token.

what I have so far:

to make Apple verification call:

        restTemplate = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("client_id", clientId); // app_id like com.app.id
        String token = generateJWT();   // generated jwt
        map.add("client_secret", token); 
        map.add("grant_type", "authorization_code");
        map.add("code", authorizationCode);  // JWT code we got from iOS
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

        final String appleAuthURL = "https://appleid.apple.com/auth/token";
        String response = restTemplate.postForObject(appleAuthURL, request, String.class);

token generation:

        final PrivateKey privateKey = getPrivateKey();
        final int expiration = 1000 * 60 * 5;

        String token = Jwts.builder()
                .setHeaderParam(JwsHeader.KEY_ID, keyId) // key id I got from Apple 
                .setIssuer(teamId)  
                .setAudience("https://appleid.apple.com")
                .setSubject(clientId) // app id com.app.id
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .signWith(SignatureAlgorithm.ES256, privateKey) // ECDSA using P-256 and SHA-256
                .compact();

        return token;

to get my private key from the file:

        final Reader pemReader = new StringReader(getKeyData());
        final PEMParser pemParser = new PEMParser(pemReader);
        final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
        final PrivateKey pKey = converter.getPrivateKey(object);

I confirmed my JWT has all required fields:

{
  "kid": "SAME KEY AS MY KEY ID",
  "alg": "ES256"
}

{
  "iss": "Blahblah",
  "aud": "https://appleid.apple.com",
  "sub": "com.app.id",
  "exp": 1578513833,
  "iat": 1578513533
}
like image 807
Irina Avatar asked Jan 08 '20 22:01

Irina


People also ask

How do I find my Apple client ID?

First, you need to have a client id to represent your application. Apple called this Services ID in the Apple developer portal. Define the name of the app that the user will see during the login flow, as well as define the identifier, which becomes the OAuth client_id . Check the Sign In with Apple checkbox.

How do I get Apple client secret?

The client secret for Sign in with Apple is a JSON Web Token that you create and sign with a private key. You need to generate the private key through the Apple Developer Portal. Click Keys in the left navigation menu. Click the blue plus icon next to Keys.

What does invalid client ID mean?

"errorDescription": "Invalid client ID or secret" } ] } This can be caused by an incorrect Key:Secret pairing or if the Key:Secret pairing is not being passed properly in the request.

How do I revoke an Apple token?

The requirement to revoke authorization tokens when a user deletes their account for an ios app requires two api calls to the appleid.apple.com framework. The first requires passing the authorization code to the /auth/token which returns a token that can be used to revoke app credentials.


1 Answers

This line caught my attention:

map.add("code", authorizationCode);  // JWT code we got from iOS

The authorizationCode is not a jwt

JSON Web Tokens consist of 3 parts separated by dots

but the authorizationCode has 4 parts like this:

text1.text2.0.text3

You are probably using the identityToken from the iOS app instead of the authorizationCode

This is how you retrieve it:

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

Also good to have the following in mind for those who might come here after getting the same invalid_client error:

  1. kid is the id for the private key from developer.apple.com/account/resources/authkeys/list

  2. keyFile is the file holding the private key downloaded from developer.apple.com

  3. teamID can be found by logging in to developer.apple.com and clicking on account, the teamID can be seen in the upper right corner

  4. the value in aud should be https://appleid.apple.com

  5. app_id is the bundle identifier for the app

In case it might help, here is a working solution in python to create a client_secret:

# $ pip install pyjwt
import jwt
import time

kid = "myKeyId"  
keyFile = "/pathToFile/AuthKey.p8"
key = ""
with open(keyFile, 'r') as myFile:
    key = myFile.read()

print(key)

timeNow = int(round(time.time()))
time3Months = timeNow + 86400*90

claims = {
    'iss': teamID,
    'iat': timeNow,
    'exp': time3Months,
    'aud': 'https://appleid.apple.com',
    'sub': app_id,
}


secret = jwt.encode(claims, key, algorithm='ES256', headers={'kid': kid})
print("secret:")
print(secret)
client_secret = secret.decode("utf-8")
print(client_secret)
like image 164
DevB2F Avatar answered Sep 22 '22 05:09

DevB2F