Closed cubevis closed 2 years ago
Does your JWT token look valid, according to the library itself? What is the response from the curl request?
@Ocramius, thanks for response, the response from curl :
HTTP/1.1 401 Unauthorized Server: daiquiri/3.0.0 Date: Wed, 02 Feb 2022 09:33:14 GMT Content-Type: application/json Content-Length: 350 Connection: keep-alive Strict-Transport-Security: max-age=31536000; includeSubDomains X-Apple-Jingle-Correlation-Key: XXXXXXXXXXXXXX x-daiquiri-instance: daiquiri:18493001:mr85p00it-hyhk03154801:7987:21RELEASE207:daiquiri-amp-all-shared-ext-001-mr { "errors": [{ "status": "401", "code": "NOT_AUTHORIZED", "title": "Authentication credentials are missing or invalid.", "detail": "Provide a properly configured and signed bearer token, and make sure that it has not expired. Learn more about Generating Tokens for API Requests https://developer.apple.com/go/?id=api-generating-tokens" }] },
The token looks valid but when I paste this to let's say : jwt.io, it says: Invalid Signature
I paste this to let's say : jwt.io, it says: Invalid Signature
The signature needs to be validated against your key - I don't suggest pasting your key into jwt.io
, but to validate it locally against the Parser
provided by this library.
@Ocramius, so according to liblary: Parser, how to validate my generated JWT using PHP? I will dive into liblary right now, but if are more familiar with this, please dive with me :)
How did you convert the p8 certificate? Is this library able to verify the signature using the converted public key? Does Apple mandate any claim that you're not setting (sub)?
It also looks like you're using the date formatter that uses microseconds as precision (when the time object provides it). Does apple support that?
If they don't, try using the ChainedFormatter::withUnixTimestampDates()
as formatter.
@lcobucci , many thanks for response! :)
I converted .p8 using this line in terminal :
openssl pkcs8 -nocrypt -in AuthKey.p8 -out AuthKey.pem
But this didn't of course get me public key, so I tried another one :
curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar
curl -OL https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar.pubkey
chmod +x jose.phar
./jose.phar key:load:key ./AuthKey_2CMKR9X24G.p8 > private_key.jwk
./jose.phar key:convert:public $(cat private_key.jwk) > public_key.jwk
./jose.phar key:convert:pkcs1 $(cat private_key.jwk) > private_key.pem
./jose.phar key:convert:pkcs1 $(cat public_key.jwk) > public_key.pem
rm *.jwk
rm jose.phar*
This code finally gave me private.pem and public.pem
In terms of microseconds, could u explain more where should I do this?
Many thanks, Jake
This is how I generate and convert ECDSA keys directly via openssl:
openssl genpkey -algorithm EC -out private-key.pem \
-pkeyopt ec_paramgen_curve:P-256 \
-pkeyopt ec_param_enc:named_curve
openssl ec -in private-key.pem -pubout -out public-key.pem
With that, I'm able to issue and verify tokens:
<?php
require 'vendor/autoload.php';
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
$config = Configuration::forAsymmetricSigner(
Signer\Ecdsa\Sha256::create(),
InMemory::file(__DIR__ . '/private-key.pem'),
InMemory::file(__DIR__ . '/public-key.pem'),
);
$now = new DateTimeImmutable();
$token = $config->builder()
->issuedBy('57246542-96fe-1a63-e053-0824d011072a')
->permittedFor('appstoreconnect-v1')
->withClaim('scope', array("GET /v1/apps?filter[platform]=IOS"))
->issuedAt($now)
->expiresAt($now->modify('+1 hour'))
->getToken($config->signer(), $config->signingKey());
var_dump(
$config->validator()->validate(
$token,
new SignedWith($config->signer(), $config->verificationKey())
)
);
echo $token->toString(), PHP_EOL;
The expected output here is something like:
bool(true)
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiI1NzI0NjU0Mi05NmZlLTFhNjMtZTA1My0wODI0ZDAxMTA3MmEiLCJhdWQiOiJhcHBzdG9yZWNvbm5lY3QtdjEiLCJzY29wZSI6WyJHRVQgL3YxL2FwcHM_ZmlsdGVyW3BsYXRmb3JtXT1JT1MiXSwiaWF0IjoxNjQzODIzMTg5LjE3ODc4MSwiZXhwIjoxNjQzODI2Nzg5LjE3ODc4MX0.zhslOHOZYNZdoztRyq2qvCJr_wrVA-LABhfiOZFrrmw6YOAxuPZWINp_Ueq3DEGssp_o5l-tyo1hN912oJagwQ
Taking that token and public key to jwt.io gives you this:
As you can see, the signature is also properly verified on jwt.io.
Now, check that the timestamps on iat
and exp
claims have decimal places. That's because the DateTimeImmutable
object has microsecond precision. If you don't want that, you can make the following adjustment on the script:
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\InMemory;
+ use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
(...)
- $token = $config->builder()
+ $token = $config->builder(ChainedFormatter::withUnixTimestampDates())
->issuedBy('57246542-96fe-1a63-e053-0824d011072a')
(...)
You'll have a token like this:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiI1NzI0NjU0Mi05NmZlLTFhNjMtZTA1My0wODI0ZDAxMTA3MmEiLCJhdWQiOiJhcHBzdG9yZWNvbm5lY3QtdjEiLCJzY29wZSI6WyJHRVQgL3YxL2FwcHM_ZmlsdGVyW3BsYXRmb3JtXT1JT1MiXSwiaWF0IjoxNjQzODIzNjY0LCJleHAiOjE2NDM4MjcyNjR9.2cAA0xlk5nqdeOjAOleX6ViChE2NttT0iX_G_rNMWULLjP9PkjETJQoxq6-ZmqUD38TnmKp8B7hxeB9HOmRLkA
Which would give you the following result on jwt.io
I also tested the PKCS8 -> PKCS1 conversion and public key extraction only with openssl using my script and got the same results (even on jwt.io)...
openssl pkcs8 -in AuthKey.p8 -out private-key.pem -nocrypt
openssl ec -in private-key.pem -pubout -out public-key.pem
I advise you to try it out (with my script and your key) and verify that it works fine on jwt.io and then trying it out with Apple.
I hope this helps you 😄
Ahhh, if your key is in DER format you should use this:
openssl pkcs8 -inform DER -in AuthKey.p8 -out private-key.pem -nocrypt
@lcobucci again many thanks for response! Let me test it and I will let u know asap! :)
@lcobucci, soo I tried and let me tell what we got :
Buuut, still for some reason Apple says : 401, Unauthorized which may be problem in somewhere else :/
So in summary :
My curl code :
$final_token = $token->toString();
$url = 'https://api.appstoreconnect.apple.com/v1/apps/';
$ch = curl_init();
$header = array();
$header[] = 'Content-length: 0';
$header[] = 'Content-type: application/json';
$header[] = 'Authorization: Bearer '.$final_token;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, TRUE);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$head = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
Maybe Apple doesn't like this .pem convert way? Maybe they want to use .p8 I'm pretty sure that this issue will be common :/
@lcobucci, this is what I also found : https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests Few important information, for example :
@lcobucci, this is what I also found : https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests Few important information, for example :
- iat: The token’s creation time, in UNIX epoch time; for example, 1528407600.
And did you try to change the claims formatter as I suggested?
Yes, still not works :(
I wrote to Apple about this, because as u can see, you helped me to finally generate JWT which has signature verified but Apple doesn't like that :/
@cubevis is that for a development environment? If not, please make sure to revoke it. Don't post this publicly to avoid compromising your credentials.
@lcobucci it is, but if we can solve this, i will of course disable this and generate new one :)
Did you see this:
The token’s expiration time in Unix epoch time. Tokens that expire more than 20 minutes into the future are not valid except for resources listed in Determine the Appropriate Token Lifetime.
Your script was issuing tokens with 1h of expiration.
@lcobucci IT WORKS ! ! ! !
Jessuuuuuus.... :D :D
@lcobucci, many thanks for your help! I think this topic should be available for everyone who :
It would be great if you could send a PR to the docs or create a discussion to share your findings.
Some remarks to make, though:
@lcobucci let me deal with it and give feedback asap! :)
Hello!
Im using this liblary to generate ES256 JWT for Apple to connect with them, but the problem is that generated token is invalid.
So in summary :
What I did and it didn't work
My Code (PHP 8.1)
What I need :
Thanks, Jake