Laravel Apple Sign in using Laravel Socialite Package

Harish Patel
4 min readJan 21, 2023

Hello Artisans…!!!!

If you are reading this article, I know you are struggling to find the information you need to implement Apple Sign-In using laravel.

There are a few examples I referred to while I integrated Apple Sign-in.

Requirement:

  • PHP 7.4+
  • Laravel 8.0+
  • Socialite 5.0+
  • Apple Developer Subscription

So let’s get started.

Installation

For Apple login, we use the Socialite package

composer require socialiteproviders/apple

Add provider event listener
Configure the package’s listener to listen for SocialiteWasCalled events.

Add the event to your listen[] array in app/Providers/EventServiceProvider. See the Base Installation Guide (opens a new window)for detailed instructions.

protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
// ... other providers
\SocialiteProviders\Apple\AppleExtendSocialite::class.'@handle',
],
];

Add configuration to config/services.php

    'apple' => [
'client_id' => env('APPLE_CLIENT_ID', ''),
'client_secret' => env('APPLE_CLIENT_SECRET', ''),
'team_id' => env('APPLE_TEAM_ID', ''),
'key_id' => env('APPLE_KEY_ID', ''),
'private_key' => env('APPLE_PRIVATE_KEY', ''),
'redirect' => env('APPLE_REDIRECT_URI', '')
],

Here are details of the env variable where you get this,

APPLE_CLIENT_ID => This will be service id of you apple app
APPLE_PRIVATE_KEY => This is secret key where you have to create from apple account
APPLE_TEAM_ID => Copy this from Apple account top right under profile picture
APPLE_KEY_ID => This will key id inside of you key in apple account
APPLE_REDIRECT_URI => This will be full callback URL after login you want to redirect
APPLE_CLIENT_SECRET => we will create it for each request using token generation.

If you don’t know how to create these values don’t worry here you find all you need to know about Okta Developer. Please refer to this doc for the generation of these details

After that add these variables to your .env file

After configuration, let's create routes for apple login. in you web.php

Route::get('/apple-login', 'UserController@loginWithApple')->name('login-with-apple');
Route::post('/apple-login-callback', 'UserController@loginWithAppleCallback')->name('apple-login-callback');

We have to add the callback URL in VerifyCsrfToken middleware to exclude. in your App\Http\Middleware\VerifyCsrfToken.php

/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'apple-login-callback'
];

The issue with these generated tokens is that they expire after 6 months, as this is the maximum lifetime that Apple will allow for the generated JWT tokens.

Whilst using the script can get you up and running quicker, it places a burden on you or your team to ensure this token is updated at least every 6 months and introduces a point of failure into your code.

A better approach, in my opinion, is to generate the JWT for your client's secret on each request. It is very fast to generate and will not add any noticeable performance decrease to your requests.

First, we create a class to generate the Apple JWT token:
Create a class in app/Services/AppleToken.php

// app/Services/AppleToken.php

<?php

namespace App\Services;

use Carbon\CarbonImmutable;
use Lcobucci\JWT\Configuration;

class AppleToken
{
private Configuration $jwtConfig;

public function __construct(Configuration $jwtConfig)
{
$this->jwtConfig = $jwtConfig;
}

public function generate()
{
$now = CarbonImmutable::now();

$token = $this->jwtConfig->builder()
->issuedBy(config('services.apple.team_id'))
->issuedAt($now)
->expiresAt($now->addHour())
->permittedFor('https://appleid.apple.com')
->relatedTo(config('services.apple.client_id'))
->withHeader('kid', config('services.apple.key_id'))
->getToken($this->jwtConfig->signer(), $this->jwtConfig->signingKey());

return $token->toString();
}
}

The class itself requires a configuration object, let’s provide this from the AuthServiceProvider. You might ask why I've chosen the AuthServiceProvider for this; the AppleToken class is directly related to authentication and thus is perfectly at home in the AuthServiceProvider, you could opt for the AppServiceProvider, but I find this usually ends up being a dumping ground for bindings that didn't find a good home anywhere else:

// app/Providers/AuthServiceProvider.php

use App\Services\AppleToken;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;

public function boot()
{
// ...other bindings or setup

$this->app->bind(Configuration::class, fn () => Configuration::forSymmetricSigner(
Sha256::create(),
InMemory::plainText(config('services.apple.private_key')),
));
}

Let's Create a App\Http\Controllers\UserController.php controller and add both methods.


use App\Services\AppleToken;
use Laravel\Socialite\Facades\Socialite;

.........

public class UserController {

public function loginWithApple(Request $request, AppleToken $appleToken) {
try {

config()->set('services.apple.client_secret', $appleToken->generate());
return Socialite::driver('apple')->redirect();

} catch (Exception $e) {
return redirect('/')->withErrors('Something went wrong please try again.');
}
}

public function loginWithAppleCallback(Request $request, AppleToken $appleToken) {
try {
config()->set('services.apple.client_secret', $appleToken->generate());
$payload = Socialite::driver('apple')->stateless()->user();
//$payload variable contain user information like email, name etc.
// use this variable for create and login user inside your app.
$user = User::where('email', $payload['email'])->first();
if ($userAccount) {
Auth::login($user);
} else {
// Create user in you database
}

} catch (Exception $e) {
return redirect('/')->withErrors('Something went wrong please try again.');
}
}
}

Few more things you need to know about apple login which struggled with while implementing this in my project.

  1. Apple API sent only first time all details like name and attributes so you have to make sure that you store all the details you need.
  2. If the customer chose to Hide My Email option in the sign-in process then you will get a private apple id email. So, in that case, you can’t send mail directly to that customer’s mail. You have to configure the Email service in your Apple Developer account here.
    https://developer.apple.com/account/resources/services/configure

--

--

Harish Patel

My self Harish Patel. I’m a Bankend Developer. I have 6 years of experience in Web development. I have expertise in Laravel, MySQL