How to Integrate Sign in with Apple in PHP (5 minute code)


December 2021

This feature is positioned as a secure and privacy-friendly way for users to create an account in apps. Most iOS and Mac users already have an Apple ID, and this new feature lets them use that Apple ID to sign in to other apps and websites.

If this sounds a lot like “Sign in with Facebook” or “Sign in with Twitter”, that’s because it is! This is Apple’s way of providing a way for users to sign in to apps without having to rely on an external identity provider like Facebook or Twitter.

Apple is taking a firm stance to protect user’s privacy with this new feature. Rather than letting apps see the user’s real email address, they will provide the app with a proxy email address unique to each app. App developers will still be able to send emails to the proxy addresses, it just means developers won’t be able to use the email addresses to correlate users between apps, and it also means users can shut off email forwarding per app.

How Sign In with Apple Works (Hint: it uses OAuth and OIDC)

Thankfully, Apple adopted the existing open standards OAuth 2.0 and OpenID Connect to use as the foundation for their new API. While they don’t explicitly call out OAuth or OIDC in their documentation, they use all the same terminology and API calls. This means if you’re familiar with these technologies, you should have no trouble using Sign in with Apple right away!

Let’s walk through building a short sample application that can leverage Apple’s new API to sign users in. You should be able to translate this code to your language of choice relatively easily once you see the requests being made.

The hardest part of this whole process is registering an application in the Apple Developer Portal. Typically, OAuth providers have a relatively straightforward process to register a new application which will give you a client ID and secret. But since Apple’s API is tied to their whole iOS app ecosystem, it’s a bit more complicated. They’ve also opted to use a public/private key client authentication method rather than a traditional client secret. But don’t worry, we’ll go through this step by step.

At the end of this process, you’ll end up with a registered client_id (which they call a Service ID), a private key downloaded as a file, and you’ll verify a domain and set up a redirect URL for the app.

First, sign in to the Apple Developer Portal and Buy the developer account for indivuals 100$ cost and then click on Certificates, Identifiers and Profiles.

Create an App ID

From the sidebar, choose Identifiers then click the blue plus icon.

Choose App IDs in this first step.

In the next screen, you’ll choose a description and Bundle ID for the App ID. The description isn’t too important, but type something descriptive. The Bundle ID is best when it’s a reverse-dns style string.

In this example, I’m using lol.avocado since the domain this app will be running on is

You’ll also want to scroll down through the list of capabilities and check the box next to Sign In with Apple.

Go ahead and confirm this step.

Create a Services ID

The App ID in the previous step is a sort of way to collect things about this app. It seems redundant when you’re only making a simple web app login experience like this example, but it makes more sense once you have a native app and web app that you want to tie together under a single login experience.

The Services ID will identify the particular instance of your app, and is used as the OAuth client_id.

Go ahead and create a new identifier and choose Services IDs.

In the next step, you’ll define the name of the app that the user will see during the login flow, as well as define the identifier which becomes the OAuth client_id. Make sure to also check the Sign In with Apple checkbox.

You’ll also need to click the Configure button next to Sign In with Apple in this step. This is where you’ll define the domain your app is running on, as well as define the redirect URLs used during the OAuth flow.

Make sure your associated App ID is chosen as the Primary App ID. (If this is the first App ID you’ve made that uses Sign In with Apple, then it will probably already be selected.)

Enter the domain name your app will eventually be running at, and enter the redirect URL for your app as well. If you want to follow along with this blog post, then you can use the placeholder redirect URL which will catch the redirect so you can see the authorization code returned.

It’s worth noting that Apple doesn’t allow localhost URLs in this step, and if you enter an IP address like it will fail later in the flow. You have to use a real domain here.

Go ahead and click Save and then Continue and Register until this step is all confirmed.

Ok! At this point, you now have an App ID container to hold everything, and you’ve created a Services ID which you’ll use as your OAuth client_id. The Identifier you entered for your Services ID is your OAuth client_id. In my example, that is lol.avocado.client.

Create a Private Key for Client Authentication

Rather than using simple strings as OAuth client secrets, Apple has decided to use a public/private key pair, where the client secret is actually a signed JWT. This next step involves registering a new private key with Apple.

Back in the main Certificates, Identifiers & Profiles screen, choose Keys from the side navigation.

Click the blue plus icon to register a new key. Give your key a name, and check the Sign In with Apple checkbox.

Click the Configure button and select your primary App ID you created earlier.

Apple will generate a new private key for you and let you download it only once. Make sure you save this file, because you won’t be able to get it back again later! The file you download will end in .p8, but it’s just text inside, so rename it to key.txt so that it’s easier to use in the next steps.

Lastly, go back and view the key information to find your Key ID which you’ll need in the next step.

Alright, that was a lot, but we are now ready to start writing some code! If you ran into any trouble, try checking out Apple’s documentation in case anything has changed since the publication of this blog post.

Generate the Client Secret

Rather than static client secrets, Apple requires that you derive a client secret yourself from your private key. They use the JWT standard for this, using an elliptic curve algorithm with a P-256 curve and SHA256 hash. In other words, they use the ES256 JWT algorithm. Some JWT libraries don’t support elliptic curve methods, so make sure yours does before you start trying this out.

The Ruby JWT library supports this algorithm, so we’ll use that to generate the secret.

First, make sure you’ve got Ruby installed, and then install the JWT gem by running this from the command line:

gem install jwt

Now the jwt gem should be available for use. Fill in the missing values at the top of this file, and save it as client_secret.rb. You should already have the client_id value from the previous step. You’ll also need your Apple Team ID. This is displayed in a few places, but the most convenient is in the top right corner of the screen. Use the Key ID you found in the previous step.


require 'jwt'

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

ecdsa_key = key_file

headers = {
  'kid' => key_id

claims = {
	'iss' => team_id,
	'iat' =>,
	'exp' => + 86400*180,
	'aud' => '',
	'sub' => client_id,

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

puts token

This code generates a JWT using the ES256 algorithm which includes a handful of claims. This JWT expires in 6 months, which is the maximum lifetime Apple will allow. If you’re generating a new client secret JWT every time a user authenticates, then you should use a much shorter expiration date, but this allows us to generate the secret once and use it in our sample apps easily.

Now you can run this from the command line and it will output a JWT.

ruby client_secret.rb




require 'vendor/autoload.php';

use FirebaseJWTJWT;


$teamId = 'TEAM ID';

$keyId = 'KEY ID';

$sub = 'com.avocado.client';

$aud = ''; // it's a fixed URL value

$iat = strtotime('now');

$exp = strtotime('+60days');

$keyContent = file_get_contents('key.txt');


echo JWT::encode([

    'iss' => $teamId,

    'iat' => $iat,

    'exp' => $exp,

    'aud' => $aud,

    'sub' => $sub,

], $keyContent, 'ES256', $keyId);

// Write the snippet in a method, return the value from that method

// You



This is described in Apple’s documentation Generate and validate tokens.


Begin the OAuth 2.0 Flow

Now that we’ve got a client secret, we can actually begin the OAuth flow! For this step, we’ll create a simple PHP file that will handle both the beginning of the flow as well as the redirect step.

Go ahead and create a file called index.php with the following contents, filling in the values for your own application.



$client_id = '';
$client_secret = '';
$redirect_uri = '';

Now you can start building the URL that the user will go visit to start the flow. First, you’ll generate a random state value and save it in the session. Then you’ll use your app’s configured values to create a URL that the user can visit. These parameters should all look familiar to you if you’ve used OAuth or OpenID Connect before.

I couldn’t find any documentation on which URL to use as the authorization endpoint, or even whether these were the right parameters, but thankfully the rest of the API looked like OAuth so I was able to figure it out despite the missing docs.

$_SESSION['state'] = bin2hex(random_bytes(5));

$authorize_url = ''.'?'.http_build_query([
  'response_type' => 'code',
  'response_mode' => 'form_post',
  'client_id' => $client_id,
  'redirect_uri' => $redirect_uri,
  'state' => $_SESSION['state'],
  'scope' => 'name email',

Now you can create a link the user can click to log in.

echo '<a href="'.$authorize_url.'">Sign In with Apple</a>';

Of course, you should probably use the recommended button provided by Apple so that it looks a bit nicer than this text link, but this is good enough for now.

You can run this app now using the built-in PHP web server!

php -S

Once that’s running, you can visit in your browser and you should see your sign-in link!

Handle the OAuth Redirect

Before you click Sign In, you should set up the mechanism for handling the redirect back from Apple.

Add the following code to index.php above the line $_SESSION['state'] = bin2hex(random_bytes(5));.

if(isset($_POST['code'])) {

  if($_SESSION['state'] != $_POST['state']) {
    die('Authorization server returned an invalid state parameter');

  // Token endpoint docs: 

  $response = http('', [
    'grant_type' => 'authorization_code',
    'code' => $_POST['code'],
    'redirect_uri' => $redirect_uri,
    'client_id' => $client_id,
    'client_secret' => $client_secret,

  if(!isset($response->access_token)) {
    echo '<p>Error getting an access token:</p>';
    echo '<pre>'; print_r($response); echo '</pre>';
    echo '<p><a href="/">Start Over</a></p>';

  echo '<h3>Access Token Response</h3>';
  echo '<pre>'; print_r($response); echo '</pre>';

  $claims = explode('.', $response->id_token)[1];
  $claims = json_decode(base64_decode($claims));

  echo '<h3>Parsed ID Token</h3>';
  echo '<pre>';
  echo '</pre>';


This is a handful of code, so let’s walk through it.


First, we check for the presence of the authorization code in the query string which indicates that Apple has completed the initial step and sent the user back to the app.

Next, we verify the state parameter matches the one we set at the beginning. This is to protect your app against CSRF attacks.

Then we’re ready to exchange the authorization code for an access token. Again this will look familiar if you’ve written OAuth code before.

This request is actually documented on Apple’s website, although as of this writing, their docs have a couple of typos. The typos make it look like Apple has done something non-standard with the parameter names, but in reality, the OAuth parameters work properly.

  $response = http('', [
    'grant_type' => 'authorization_code',
    'code' => $_POST['code'],
    'redirect_uri' => $redirect_uri,
    'client_id' => $client_id,
    'client_secret' => $client_secret,

This uses a helper function http() that we need to define, so go ahead and add the following to the bottom of your index.php file.

function http($url, $params=false) {
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Accept: application/json',
    'User-Agent: curl', # Apple requires a user agent header at the token endpoint
  $response = curl_exec($ch);
  return json_decode($response);

One particularly tricky thing I noticed while testing this is that Apple requires the presence of a User-Agent header in the HTTP request, otherwise, it returns a 403 error and an HTML web page. This is not documented in their API, because I’m guessing this check happens at their API gateways before the request hits the real backend server.

At this point, assuming everything worked, you can output the response from this and see what Apple has returned!

Let’s try this out for reals now.

Try Sign In with Apple Now!

Click the “Sign In with Apple” button, and you’ll be taken to the Apple sign-in screen..

You should see the name of your app and a placeholder icon. Enter your Apple ID and password.

If your Apple ID has two-factor auth enabled, you’ll be prompted for that as well.

It does seem to prompt for two-factor auth every time you log in to an app, which is definitely secure, but can be a little frustrating especially while testing.

Once you confirm that, you’ll see a screen asking if you would like to continue signing in to the app.

Note: You will only see this permissions screen the very first time you log in with this App ID. In subsequent logins, you’ll see a confirmation prompt like the below.

If you want to reset this so that you see the permissions screen and get the name and email back from Apple, visit your Apple ID App Security page and revoke your application.

Now click Continue and you’ll be redirected to your redirect URL! If you entered the placeholder URL then you’ll see a message like this.

Since Apple sends a POST request to the redirect URL, you’ll have to actually set up a redirect handler yourself to catch the values, since the authorization code will likely expire before you can copy it out of this debug screen.

Once you’ve set up the sample code above and registered your own redirect URL, your app will exchange the authorization code for an access token and ID token, and will show the output on the screen!

The last step to getting the user’s info is to decode the ID token. Since in this example we used response_type=code to get the ID token, the ID token was obtained via the back channel, which means we don’t need to worry about validating the JWT signature of the ID token. We can just parse out the middle claims section and read the data directly.

The sub value in the claims is the unique identifier for the user. It’s notable that this value doesn’t mean anything in particular, which is Apple’s way of preserving user privacy. You can store this value in your own database now, and use it to determine whether the same user logged back in a second time.

You can also find the user’s email or proxy email in the claims as well.

Note: Apple will send the user’s name and email in the form post response back to your redirect URL. You should not treat these values as authoritative, because like the OAuth Implicit flow, the data cannot be safely trusted at this point. Unfortunately Apple does not return the user’s name in the ID token where it would be safe to trust. Thankfully they do return the user’s email address that way, so that’s where you should get it from.

Contact us for more details.



What separates good content from great content is a willingness to take risks and push the envelope.

Comments (0)

Leave a comment