Add Apple's new single-signon feature to your site with ease.
genealabs/laravel-sign-in-with-apple is a Laravel package for add apple's new single-signon feature to your site with ease..
It currently has 481 GitHub stars and 654.315 downloads on Packagist (latest version 13.0.0).
Install it with composer require genealabs/laravel-sign-in-with-apple.
Discover more Laravel packages by genealabs
or browse all Laravel packages to compare alternatives.
Last updated

This is an MIT-licensed open source project with its ongoing development made possible by the support of the community. If you'd like to support this, and our other packages, please consider sponsoring us via the button above.
We thank the following sponsors for their generosity, please take a moment to check them out:
| Laravel | PHP | Package | |---------|-----------|----------| | 10.x | 8.2+ | 5.x | | 11.x | 8.2+ | 5.x | | 12.x | 8.2+ | 5.x | | 13.x | 8.3+ | 5.x |
Install the composer package:
composer require mikebronner/laravel-sign-in-with-apple
We also recommend using geneaLabs/laravel-socialiter to automatically manage user resolution and persistence:
composer require genealabs/laravel-socialiter
App ID for your website (https://developer.apple.com/account/resources/identifiers/list/bundleId) with the following details:
Service ID for your website (https://developer.apple.com/account/resources/identifiers/list/serviceId) with the following details:
Private Key for your website (https://developer.apple.com/account/resources/authkeys/list) with the following details:
key.txtInstall the JWT Gem:
sudo gem install jwt
Create a file called client_secret.rb to process the private key:
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
Fill in the following fields:
team_id: This can be found on the top-right corner when logged into
your Apple Developer account, right under your name.client_id: This is the identifier from the Service Id created in step
2 above, for example com.example.servicekey_id: This is the identifier of the private key created in step 3
above.Save the file and run it from the terminal. It will spit out a JWT which is
your client secret, which you will need to add to your .env file in the
next step.
ruby client_secret.rb
Instead of using the Ruby script above, you can generate the client secret JWT directly in PHP using this package's built-in helper:
use GeneaLabs\LaravelSignInWithApple\Support\ClientSecretGenerator;
// One-off generation
$secret = ClientSecretGenerator::generate(
teamId: 'YOUR_TEAM_ID',
clientId: 'com.example.service',
keyId: 'YOUR_KEY_ID',
privateKey: file_get_contents(storage_path('keys/apple-auth-key.p8')),
ttlDays: 180, // Max 180 days
);
// Or use config/env values automatically
$secret = ClientSecretGenerator::fromConfig();
You can use an Artisan command or scheduled task to auto-rotate the secret before it expires:
// In a scheduled command or service provider
$secret = ClientSecretGenerator::fromConfig(ttlDays: 180);
config(['services.sign_in_with_apple.client_secret' => $secret]);
Required env vars for fromConfig():
SIGN_IN_WITH_APPLE_TEAM_ID=your-team-id
SIGN_IN_WITH_APPLE_KEY_ID=your-key-id
SIGN_IN_WITH_APPLE_PRIVATE_KEY_PATH=/path/to/key.p8
Set the necessary environment variables in your .env file:
APPLE_REDIRECT="/apple/login/controller/callback/action"
APPLE_CLIENT_ID="your app's service id as registered with Apple"
APPLE_CLIENT_SECRET="your app's client secret as calculated in step 4"
Note: The
APPLE_LOGINenvironment variable has been removed (previouslySIGN_IN_WITH_APPLE_LOGIN). Login routes should be defined in your application's route files instead. See the Migration Guide below if upgrading from an older version.
Apple has strict requirements for the redirect (callback) URL:
http://localhost is allowed for local development.The package validates your redirect URL at auth initiation and throws an InvalidRedirectUrlException with a clear error message if it doesn't meet these requirements.
Common mistakes:
http:// instead of https:// in productionAdd the following blade directive to your login page:
@signInWithApple($color, $hasBorder, $type, $borderRadius)
| Parameter | Definition |
| --------- | ---------- |
| $color | String, either "black" or "white. |
| $hasBorder | Boolean, either true or false. |
| $type | String, either "sign-in" or "continue". |
| $borderRadius | Integer, greater or equal to 0. |
Apple sends the authorization response as a POST request to your callback URL. This would normally trigger a 419 | Page Expired (CSRF token mismatch) error. This package automatically excludes the configured callback route from CSRF verification, so no additional configuration is required.
This is safe because Apple callbacks are validated via the OAuth state parameter, not CSRF tokens. If you need to manually exclude the route for any reason, you can use one of these approaches:
Option A: Exclude the route in your VerifyCsrfToken middleware (Laravel 10 and earlier):
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'/apple/callback', // or whatever your callback URL is
];
Option B: Use withoutMiddleware on the route (Laravel 11+):
Route::post('/apple/callback', [AppleSigninController::class, 'callback'])
->withoutMiddleware([\\Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken::class]);
This implementation uses Socialite to get the login credentials. The following is an example implementation of the controller:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use GeneaLabs\LaravelSocialiter\Facades\Socialiter;
use Laravel\Socialite\Facades\Socialite;
class AppleSigninController extends Controller
{
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function login()
{
return Socialite::driver("sign-in-with-apple")
->scopes(["name", "email"])
->redirect();
}
public function callback(Request $request)
{
// get abstract user object, not persisted
$user = Socialite::driver("sign-in-with-apple")
->user();
// or use Socialiter to automatically manage user resolution and persistence
$user = Socialiter::driver("sign-in-with-apple")
->login();
}
}
Note that when processing the returned $user object, it is critical to know that the sub element is the unique identifier for the user, NOT the email address. For more details, visit https://developer.apple.com/documentation/signinwithapplerestapi/authenticating_users_with_sign_in_with_apple.
If you receive an error about a missing authorization code in the callback, check:
Your callback route must accept POST requests — Apple uses response_mode=form_post, which means the authorization code is sent as a POST form parameter, not a URL query parameter. Use Route::post(), not Route::get().
CSRF protection must be disabled for the callback — Since Apple's POST doesn't include a CSRF token, Laravel will return a 419 error and the code will never reach your controller. See the CSRF Exclusion section above.
The redirect URL must exactly match — The URL in your .env (APPLE_REDIRECT) must exactly match the Return URL configured in your Apple Developer account, including the protocol, domain, and path.
When a user revokes your app's access via Apple ID settings, Apple sends a server-to-server notification. This package provides an AppleNotificationController and AppleAccessRevoked event to handle this.
1. Register the notification route:
use GeneaLabs\LaravelSignInWithApple\Http\Controllers\AppleNotificationController;
Route::post('/apple/notifications', [AppleNotificationController::class, 'handle'])
->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
2. Listen for the revocation event:
// In EventServiceProvider or a listener
use GeneaLabs\LaravelSignInWithApple\Events\AppleAccessRevoked;
Event::listen(AppleAccessRevoked::class, function (AppleAccessRevoked $event) {
// $event->sub — the Apple user ID
// $event->eventType — 'consent-revoked' or 'account-delete'
$user = User::where('apple_id', $event->sub)->first();
if ($user) {
// Deactivate, log out, or clean up
}
});
3. Configure the endpoint in Apple Developer:
Add your notification URL (https://example.com/apple/notifications) in the Apple Developer portal under your Services ID configuration.
Important: Apple only provides the user's name and email on the first authorization. If a user revokes access and re-authenticates, Apple treats it as a new sign-in but may not provide the name again. Always store the user's name on first sign-in.
Handling re-authentication after revocation:
When a user revokes and re-authenticates, Apple may assign a new sub value. The Socialite user object includes an is_returning_user flag to help you detect this:
$appleUser = Socialite::driver('sign-in-with-apple')->user();
if ($appleUser['is_returning_user']) {
// User re-authenticated after revocation — match by email
$user = User::where('email', $appleUser->getEmail())->first();
} else {
// First-time sign-in — name is available
$user = User::firstOrCreate(
['apple_id' => $appleUser->getId()],
['name' => $appleUser->getName(), 'email' => $appleUser->getEmail()]
);
}
Security: The notification endpoint verifies Apple's JWT signatures against Apple's public keys (fetched from https://appleid.apple.com/auth/keys). Keys are cached for 1 hour. Unsigned or forged notifications are rejected.
This package includes unit, feature, and browser tests. Unit and feature tests run without any additional dependencies:
vendor/bin/phpunit --testsuite=Unit,Feature
Browser tests use Laravel Dusk via orchestra/testbench-dusk and require a Chrome-based browser installed on your machine.
Install Chrome or Chromium:
Google Chrome (recommended):
# macOS
brew install --cask google-chrome
# Ubuntu/Debian
sudo apt-get install google-chrome-stable
Chromium (lighter alternative):
# macOS
brew install --cask chromium
On macOS, Chromium may be blocked by Gatekeeper. Remove the quarantine flag after installing:
xattr -dr com.apple.quarantine /Applications/Chromium.app
Install Chromedriver:
The package uses orchestra/dusk-updater (included as a dev dependency) to manage the Chromedriver binary. Run this to auto-detect your Chrome version and install the matching Chromedriver:
# If Google Chrome is installed (auto-detected):
vendor/bin/dusk-updater detect --auto-update --no-interaction
# If using Chromium on macOS (must specify the binary path):
vendor/bin/dusk-updater detect --chrome-dir="/Applications/Chromium.app/Contents/MacOS/Chromium" --auto-update --no-interaction
Run browser tests:
vendor/bin/phpunit --testsuite=Browser
Run all tests:
vendor/bin/phpunit
The configuration has been simplified. The following changes were made:
| Old Key | New Behavior |
|---------|-------------|
| SIGN_IN_WITH_APPLE_LOGIN | Removed. Define your login route in your application's route files instead of the config. |
| SIGN_IN_WITH_APPLE_REDIRECT | Renamed to APPLE_REDIRECT. Old name still works as fallback. |
| SIGN_IN_WITH_APPLE_CLIENT_ID | Renamed to APPLE_CLIENT_ID. Old name still works as fallback. |
| SIGN_IN_WITH_APPLE_CLIENT_SECRET | Renamed to APPLE_CLIENT_SECRET. Old name still works as fallback. |
Steps to upgrade:
SIGN_IN_WITH_APPLE_LOGIN from your .env file.SIGN_IN_WITH_APPLE_REDIRECT → APPLE_REDIRECT, SIGN_IN_WITH_APPLE_CLIENT_ID → APPLE_CLIENT_ID, SIGN_IN_WITH_APPLE_CLIENT_SECRET → APPLE_CLIENT_SECRET. The old names continue to work as fallbacks.login config key for the @signInWithApple Blade directive button URL, define the route in your application's routes file and update the directive or link accordingly.E_USER_DEPRECATED notice if the old login key is still present, giving you time to migrate before it is fully removed.During package development I try as best as possible to embrace good design and development practices, to help ensure that this package is as good as it can be. My checklist for package development includes:
invalid_client ErrorThis means Apple rejected your credentials. Common causes:
APPLE_CLIENT_ID must be your Services ID (not your App ID or Team ID).invalid_grant ErrorThis means the authorization code was rejected. Common causes:
APPLE_REDIRECT must exactly match the URL registered in your Apple Developer account.If you see Sign In With Apple is missing required config, ensure you have set the following in your .env:
APPLE_CLIENT_ID=your-services-id
APPLE_CLIENT_SECRET=your-generated-jwt
APPLE_REDIRECT=https://your-app.com/callback
Please observe and respect all aspects of the included Code of Conduct.
When reporting issues, please fill out the included template as completely as possible. Incomplete issues may be ignored or closed if there is not enough information included to be actionable.
Please review the Contribution Guidelines. Only PRs that meet all criterium will be accepted.
We have included the awesome symfony/thanks composer package as a dev dependency. Let your OS package maintainers know you appreciate them by starring the packages you use. Simply run composer thanks after installing this package. (And not to worry, since it's a dev-dependency it won't be installed in your live environment.)