I'm trying to authenticate to Apple's AppStoreConnect API with an ES256 signed JWT (per their instructions at https://developer.apple.com/documentation/appstoreconnectapi) using PHP.
Sending my request always results in a 401 NOT_AUTHORIZED error.
I've verified that the the contents of my header and claims are correct - I even found a Ruby script online for generating an ES256 signed JWT and using my Apple provided Issuer, Key ID, Private Key, it works swimmingly - Apple accepts the token. That tells me that my credentials are good and I'm doing something wrong in php.
Unless I've simply stared at this code for too long, the JWT format is correct, base64 encoded correctly, and the bearer token is set correctly in the header.
To rule out an issue with request sending I've tried both GuzzleHTTP and CLI cURL - both a 401.
Here's the relevant code. You'll see that the create method is encoding the header and claims, signing the "payload", and concatenating all 3.
public function create()
{
$header = $this->encode(
json_encode([
'kid' => 'my_key_id',
'alg' => 'ES256',
'typ' => 'JWT',
])
);
$claims = $this->encode(
json_encode([
'iss' => 'my_issuer_uuid',
'exp' => time() + (20 * 60),
'aud' => 'appstoreconnect-v1',
])
);
$signature = $this->encode(
$this->sign("$header.$claims")
);
return $header . '.' . $claims . '.' . $signature;
}
This code successfully returns an open ssl resource, $data has the expected contents.
public function sign($data)
{
if (!$key = openssl_pkey_get_private('file://my_key_file.p8')) {
throw new \Exception('Failed to read PEM');
}
if (!openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256)) {
throw new \Exception('Claims signing failed');
}
return $signature;
}
Base64 URL encoding... $data has the expected contents.
public function encode($data)
{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}
At this point I'm stumped to what it is I'm doing wrong or missing. I'm hoping some extra eyes will find something! Using the token that my code dumps out:
curl https://api.appstoreconnect.apple.com/v1/users --Header "Authorization: Bearer <token>”
...always returns a 401. I suspect there's something wrong in the signing portion of the code as it's the only part I haven't been able to verify (again, worked in Ruby), though looking at all the docs and examples for openssl_sign, I'm pretty sure it's right.
For reference, this is the Ruby script I mention https://shashikantjagtap.net/generating-jwt-tokens-for-app-store-connect-api/