I'm trying to implement Apple sign in into an Android App using this library. The main flow is described in the documentation: the library returns an authorization code on the Android side. This authorization code has to be sent to my backend which, in turn, sends it to the Apple servers in order to get back an access token.
As described here and here, in order to obtain the access token we need to send to the Apple API a list of parameters, the authorization code and a signed JWT. In particular, JWT needs to be signed with a ES256 algorithm using a private .p8 key which has to be generated and downloaded from the Apple developer portal. Apple doc
Here is my PHP script:
<?php
$authorization_code = $_POST('auth_code');
$privateKey = <<<EOD
-----BEGIN PRIVATE KEY-----
my_private_key_downloaded_from_apple_developer_portal (.p8 format)
-----END PRIVATE KEY-----
EOD;
$kid = 'key_id_of_the_private_key'; //Generated in Apple developer Portal
$iss = 'team_id_of_my_developer_profile';
$sub = 'it.b810group.serviceId.Tippy';
$client_id = 'identifier_setted_in_developer_portal'; //Generated in Apple developer Portal
$signed_jwt = $this->generateJWT($kid, $iss, $client_id, $privateKey);
$data = [
'client_id' => $client_id,
'client_secret' => $signed_jwt,
'code' => $authorization_code,
'grant_type' => 'authorization_code'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://appleid.apple.com/auth/token');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$serverOutput = curl_exec($ch);
curl_close ($ch);
var_dump($serverOutput);
function generateJWT($kid, $iss, $sub, $key) {
$header = [
'alg' => 'ES256',
'kid' => $kid
];
$body = [
'iss' => $iss,
'iat' => time(),
'exp' => time() + 3600,
'aud' => 'https://appleid.apple.com',
'sub' => $sub
];
$privKey = openssl_pkey_get_private($key);
if (!$privKey) return false;
$payload = $this->encode(json_encode($header)).'.'.$this->encode(json_encode($body));
$signature = '';
$success = openssl_sign($payload, $signature, $privKey, OPENSSL_ALGO_SHA256);
if (!$success) return false;
return $payload.'.'.$this->encode($signature);
}
function encode($data) {
$encoded = strtr(base64_encode($data), '+/', '-_');
return rtrim($encoded, '=');
}
?>
The problem is that the response from Apple is always:
{"error":"invalid_client"}
Reading here it seems that the problem could be related to openSSL which generates a signature which is not correct for Apple ("OpenSSL's ES256 signature result is a DER-encoded ASN.1 structure (it's size exceed 64). (not a raw R || S value)").
Is there a way to obtain the correct signature using openSSL?
Is the p8 format the correct input for the openssl_sign and openssl_pkey_get_private functions? (I noticed that the provided .p8 key does not work if used in jwt.io in order to compute the signed jwt.)
In the openSSL documentation I read that a pem key should be provided, how can I convert a .p8 in a .pem key?
I also tried with some PHP libraries which basically use the same steps described above like firebase/php-jwt and lcobucci/jwt but the Apple response is still "invalid client".
Thank you in advance for your help,
EDIT
I tried to completely remove openSSL from the equation. Using the .pem key generated from the .p8 one I have generated a signed JWT with jwt.io. With this signed JWT the Apple API replies correctly. At this point I'm almost sure it's an openSSL signature problem. The key problem is how to obtain a proper ES256 signature using PHP and openSSL.