用于Apple AppStoreConenct API Auth的ES256 JWT登录PHP

时间:2019-06-16 13:37:45

标签: php jwt sha256 ecdsa appstoreconnect

我正在尝试使用PHP使用ES256签名的JWT(根据其在https://developer.apple.com/documentation/appstoreconnectapi上的说明)对Apple的AppStoreConnect API进行身份验证。

发送请求总是会导致401 NOT_AUTHORIZED错误。

我已经验证了标头和声明的内容是否正确-我什至在网上找到了一个Ruby脚本,用于生成ES256签名的JWT,并使用我的Apple提供的Issuer,Key ID,Private Key,它可以正常工作-Apple接受令牌。这说明我的凭据很好,并且在php中做错了事。

除非我只是盯着这段代码太长时间,否则JWT格式是正确的,base64编码是正确的,并且承载令牌在标头中的设置是正确的。

要排除请求发送方面的问题,我尝试了GuzzleHTTP和CLI cURL(都为401)。


这是相关的代码。您会看到create方法正在对标头和声明进行编码,对“有效载荷”进行签名,并串联所有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;
}

此代码成功返回一个打开的ssl资源,$data具有预期的内容。

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编码... $data具有预期的内容。

public function encode($data)
{
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}

在这一点上,我很困惑我做错了什么或想念什么。我希望更多的眼睛能找到一些东西!使用我的代码转储的令牌:

curl  https://api.appstoreconnect.apple.com/v1/users --Header "Authorization: Bearer <token>”

...总是返回一个401。我怀疑代码的签名部分出了问题,因为尽管查看了openssl_sign的所有文档和示例,但它是我唯一无法验证的部分(再次在Ruby中工作)。我很确定这是正确的。

作为参考,这是我提到的https://shashikantjagtap.net/generating-jwt-tokens-for-app-store-connect-api/

Ruby脚本

4 个答案:

答案 0 :(得分:0)

OpenSSL返回的签名是一个包含附加信息的ASN.1序列。在连接之前,您必须删除多余的数据。

您可以使用该simple class I wrote(方法"$$name")来转换OpenSSL签名。

答案 1 :(得分:0)

经过很多努力,我终于使用https://github.com/lcobucci/jwt

use Curl\Curl;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;

$signer = new Sha256();
$privateKey = new Key('file://AuthKey_XYZ.p8');
$time = time();

$Issuer_ID = "FROM_APPLE_PAGE";
$Key_ID = "FROM_APPLE_PAGE";

$token = (new Builder())->issuedBy($Issuer_ID)// Configures the issuer (iss claim)
->permittedFor("appstoreconnect-v1")// Configures the audience (aud claim)
->identifiedBy('XXYYZZ', true)// Configures the id (jti claim), replicating as a header item
->withHeader('kid', $Key_ID)
->withHeader('type', 'JWT')
    ->withHeader('alg', 'ES256')
    ->issuedAt($time)// Configures the time that the token was issue (iat claim)
    ->expiresAt($time + 1200)// Configures the expiration time of the token (exp claim)
    ->withClaim('uid', 1)// Configures a new claim, called "uid"
    ->getToken($signer, $privateKey); // Retrieves the generated token


$token->getHeaders(); // Retrieves the token headers
$token->getClaims(); // Retrieves the token claims

答案 2 :(得分:0)

经过大量测试后,对我有用的是

安装此软件包:

composer require lcobucci/jwt

尝试立即生成令牌:

<?php

require 'vendor/autoload.php';

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Ecdsa\Sha256; 

$signer = new Sha256();
$key = file_get_contents('AuthKey_xxxx.p8');

$privateKey = new Key($key);
$time = time();

$token = (new Builder())->issuedBy('AppleTeamID') // Configures the issuer (iss claim)
    ->withHeader('alg', 'ES256')
    ->withHeader('kid', 'AppleKeyID')
    ->issuedAt($time) // Configures the time that the token was issue (iat claim)
    ->expiresAt($time + 1200) // Configures the expiration time of the token (exp claim)
    ->getToken($signer, $privateKey); // Retrieves the generated token

// Test if your key works OK
exec("curl -v -H 'Authorization: Bearer {$token}' \"https://api.music.apple.com/v1/catalog/us/artists/36954\"");


exit;

答案 3 :(得分:0)

您可以使用composer require firebase/php-jwt来生成JWT。

首先,读取私钥文件。 freadfile_get_contentsSplFileObjectenv文件中适合您的选择。只需获取.p8文件的内容即可。

然后

// $filePath = 'file:///var/www/html/AuthKey_KEY-ID-HERE-(JUST_IGNORE).p8'
JWT::encode([
    'iss' => $teamId, // 10-character team id, under your name
    'iat' => $iat, // use strtotime('now') or Carbon::now()->timestamp
    'exp' => $exp, // use strtotime('+60 days') or Carbon::now()->days(60)->timestamp
    'aud' => "https://appleid.apple.com", // till date, it's constant
    'sub' => $sub, // Service ID identifier in https://developer.apple.com/account/resources/identifiers/list/serviceId, the id where you registered your "REDIRECT_URI"
], (new Key($filePath))->getContent(), 'ES256', $keyId); // Used Lcobucci\JWT\Signer\Key class as an experiment, and it also worked. You can you any of the above mentioned methods to get your key.

运行此命令后,您将获得client_secret作为客户端ID。接下来,您可以阅读文档。


刚刚测试,并获得了预期的结果。