Apple Sign In invalid_client, signing JWT for authentication using PHP and openSSL
As indicated here, the problem is actually in the signature generated by openSSL.
Using ES256, the digital signature is the concatenation of two unsigned integers, denoted as R and S, which are the result of the Elliptic Curve (EC) algorithm. The length of R || S is 64.
The openssl_sign function generates a signature which is a DER-encoded ASN.1 structure (with size > 64).
The solution is to convert the DER-encoded signature into a raw concatenation of the R and S values. In this library a function "fromDER" is present which perform such a conversion:
/**
* @param string $der
* @param int $partLength
*
* @return string
*/
public static function fromDER(string $der, int $partLength)
{
$hex = unpack('H*', $der)[1];
if ('30' !== mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new \RuntimeException();
}
if ('81' === mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
$hex = mb_substr($hex, 6, null, '8bit');
} else {
$hex = mb_substr($hex, 4, null, '8bit');
}
if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
$Rl = hexdec(mb_substr($hex, 2, 2, '8bit'));
$R = self::retrievePositiveInteger(mb_substr($hex, 4, $Rl * 2, '8bit'));
$R = str_pad($R, $partLength, '0', STR_PAD_LEFT);
$hex = mb_substr($hex, 4 + $Rl * 2, null, '8bit');
if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
$Sl = hexdec(mb_substr($hex, 2, 2, '8bit'));
$S = self::retrievePositiveInteger(mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = str_pad($S, $partLength, '0', STR_PAD_LEFT);
return pack('H*', $R.$S);
}
/**
* @param string $data
*
* @return string
*/
private static function preparePositiveInteger(string $data)
{
if (mb_substr($data, 0, 2, '8bit') > '7f') {
return '00'.$data;
}
while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') <= '7f') {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
/**
* @param string $data
*
* @return string
*/
private static function retrievePositiveInteger(string $data)
{
while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
Another point is that a .pem key should be provided to the open_ssl_sign function. Starting from the .p8 key downloaded from the Apple developer I have created the .pem one by using openSSL:
openssl pkcs8 -in AuthKey_KEY_ID.p8 -nocrypt -out AuthKey_KEY_ID.pem
In the following my new generateJWT function code which uses the .pem key and the fromDER function to convert the signature generated by openSSL:
function generateJWT($kid, $iss, $sub) {
$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(file_get_contents('AuthKey_.pem'));
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;
$raw_signature = $this->fromDER($signature, 64);
return $payload.'.'.$this->encode($raw_signature);
}
Hope it helps
Always occurs invalid_client error trying to get refresh token by authorisation code during sign with apple implementation
off the top of my head, try two things:
- add an Accept header:
'Accept: application/x-www-form-urlencoded'
- pass your post data not inside another array:
http_build_query($send_data)
If I think of more things I'll edit my answer.
Sign in with Apple = invalid_client
The problem was this special encryption.
In this blog they use PHP for everything except the client_secret generation.
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
And in the text the author explains this sentence:
Some JWT libraries don’t support elliptic curve methods, so make sure yours does before you start trying this out.
Now it's working fine with exactly the code in the top - only replaced the client_secret generation.
How to verify code from Sign In with Apple?
The problem for me was that I forgot to verify my domain under the Service Id section of the Apple dev portal.
You need to download the key they give you, and upload it to:
https://example.com/.well-known/apple-developer-domain-association.txt
The website doesn't verify automatically, you have to click the verify button and get a green tick next to the domain to be sure. After this, I had no more invalid_client
issues.
Update 2020.07
As the flow was changed, you just have to add the Domain and the Communication Email to:
Certificates, Identifiers & Profiles > More > Configure
Related Topics
How to Create a New Joomla User Account from Within a Script
Differencebetween Null and Empty
Message: Trying to Access Array Offset on Value of Type Null
PHP Curl Get Request and Request's Body
What Is 22527 in Error_Reporting 22527 of PHPinfo
Get Start and End Days for a Given Week in PHP
How to Get the Last Path in a Url
PHP & MySQL: When Exactly to Use HTMLentities
PHP - How to Implement Password Reset and Token Expiry
Php: Catch Exception and Continue Execution, Is It Possible
PHP Artisan Migrate Throwing [Pdo Exception] Could Not Find Driver - Using Laravel
Detect In-App Browser (Webview) with PHP/Javascript
Make Text Wrap in a Cell with Fpdf
How to Serve Mp3 Files with PHP
Laravel Cannot Delete or Update a Parent Row: a Foreign Key Constraint Fails