Laravel Apple Sign in using Laravel Socialite Package
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.
- 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.
- 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