February 15

How to generate client_secret for Apple Sign In from p8 certificate

0  comments

I was challenged to implement Apple sign-in functionality. At first, this task seemed like an easy one. Another OAuth provider I thought. Then I discovered that it requires client_secret to be generated from the p8 certificate. The entire workflow is well described in this article.

The private keys must be generated on the Apple Developer Portal.

  • Create an App ID.
  • Then, for the app, create a service ID.
  • Finally, generate the keys for your app.

I tried first the GeneaLabs/laravel-sign-in-with-apple library. They suggest using a ruby script to generate the client secret.

require 'jwt'

key_file = 'key.txt'
team_id = ''
client_id = ''
key_id = ''

ecdsa_key = OpenSSL::PKey::EC.new IO.read key_file

headers = {
'kid' => key_id
}

claims = {
    'iss' => team_id,
    'iat' => Time.now.to_i,
    'exp' => Time.now.to_i + 86400*180,
    'aud' => 'https://appleid.apple.com',
    'sub' => client_id,
}

token = JWT.encode claims, ecdsa_key, 'ES256', headers

puts token

Surprisingly, after 6 months since the client_secret was generated, I started getting an error:

Client error: POST https://appleid.apple.com/auth/token resulted in a 400 Bad Request response: {“error”:”invalid_client”}

It turned out that the client secret was expired because Apple restricted its maximum lifetime to 6 months.

I started looking for other solutions and decided to use SocialiteProviders/Apple and generate the client secret on the fly, it’s described in this article. The code looks like this:

<?php
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;

$now = new \DateTimeImmutable();

$jwtConfig = Configuration::forSymmetricSigner(
    new Sha256(),
    InMemory::file(__DIR__ . '/AuthKey.p8')
);

$token = $jwtConfig->builder()
    ->issuedBy('XXXXXXXX')
    ->issuedAt($now)
    ->expiresAt($now->modify('+1 hour'))
    ->permittedFor('https://appleid.apple.com')
    ->relatedTo('com.example.service-id')
    ->withHeader('kid', 'XXXXXXXX')
    ->getToken($jwtConfig->signer(), $jwtConfig->signingKey());

echo $token->toString();

The idea seemed great: generate the secret on each request automatically rather than manually every 6 months. But there was an issue: the p8 certificate that I downloaded from Apple wasn’t working with Lcobucci\JWT. I was getting the following error:

It was not possible to parse your key, reason: error:0909006C:PEM routines:get_name:no start line

It turned out that the p8 (PKSC#8) key needs to be converted to PKCS#1 standard. After a while a figured out the solution:

openssl pkcs8 -nocrypt -in AuthKey_XXXXXXXXXX.p8 -traditional -out AuthKey.pem

So I was able to load this the generated key either as a file

$configuration = Configuration::forSymmetricSigner(
    new Sha256(),
    InMemory::file(__DIR__ . '/AuthKey.pem)
);

Or convert it to base64 and use it as an ENV variable:

base64 -w 0 < ./AuthKey.pem
$configuration = Configuration::forSymmetricSigner(
    new Sha256(),
    InMemory::base64Encoded('Paste=here=the=base64=output===')
);


Tags

oauth2, sign-in with apple


You may also like

Leave a Reply

Your email address will not be published. Required fields are marked *

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Subscribe to our newsletter now!