Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Socialite Apple Sign In Laravel Package

nerdzlab/socialite-apple-sign-in

A Laravel Socialite provider that adds Sign in with Apple support, handling Apple's client secret generation, token exchange, and user profile parsing so you can drop Apple login into any existing Socialite flow with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a Socialite driver, not a standalone SDK — there is no facade or public API of its own. Almost everything you need to know lives in src/AppleProvider.php (JWT client-secret generation + token/userinfo calls) and the service provider/listener that calls Socialite::extend('apple', ...) on the SocialiteWasCalled event.

1. Install (alongside Socialite):

composer require laravel/socialite nerdzlab/socialite-apple-sign-in

2. Apple Developer side first (this is where most setup time goes):

  • Certificates, Identifiers & Profiles → Identifiers → Service IDs → create one, enable Sign in with Apple.
  • Configure it: primary domain (HTTPS only — localhost is rejected) and Return URLs (must match your callback URL exactly).
  • Keys → create a key with Sign in with Apple enabled, download the .p8 (only once).

3. Configure config/services.php:

'apple' => [
    'client_id'   => env('APPLE_CLIENT_ID'),   // the Service ID, e.g. com.example.web
    'team_id'     => env('APPLE_TEAM_ID'),
    'key_id'      => env('APPLE_KEY_ID'),
    'private_key' => env('APPLE_PRIVATE_KEY'), // PEM contents, not the file path
    'redirect'    => env('APPLE_REDIRECT_URI'),
],

4. First use case — a web "Sign in with Apple" button:

Route::get('/auth/apple/redirect', fn () =>
    Socialite::driver('apple')->redirect()
)->name('apple.redirect');

Route::get('/auth/apple/callback', function () {
    $appleUser = Socialite::driver('apple')->user();

    // $appleUser->getId()    -> stable `sub` claim (use this as the key)
    // $appleUser->getEmail() -> may be a @privaterelay.appleid.com address
    // $appleUser->getName()  -> NULL on every login after the first
});

Local development requires a public HTTPS tunnel (ngrok, Expose, Valet share with TLS); register the tunnel host as the Service ID domain and return URL.

Implementation Patterns

Identity table, not columns on users. Store (provider, provider_user_id, user_id, email_at_signup, name_at_signup) in a social_identities table. Lookup order on callback: match on sub → log in; else match a verified email → prompt to link; else register.

Capture the first-consent payload. Apple returns name and email only on the very first authorization for a given Apple ID + Service ID pair. Persist them at registration or you lose them permanently. Ask for the profile in your own onboarding form afterwards.

Stateless / native-app flows. For mobile clients that already hold an Apple identity token, use Socialite::driver('apple')->stateless() and avoid the session round-trip entirely. Mixing stateless and stateful in one app is the #1 source of InvalidStateException.

Wrap the driver, don't fork it. If you need extra scopes, custom Guzzle options, or a different user mapping, subclass AppleProvider and re-register it:

Socialite::extend('apple', fn ($app) =>
    new App\Socialite\AppleProvider(
        $app['request'],
        config('services.apple.client_id'),
        config('services.apple.client_secret'),
        config('services.apple.redirect'),
    )
);

Cache the generated client secret. The provider signs an ES256 JWT from the .p8 on each token exchange. That's fine for login volume but worth memoizing (e.g. Cache::remember keyed by key_id + config hash) if you also use the secret for token revocation on account deletion.

Config caching is fine. php artisan config:cache works with a PEM in an env var — but see the escaping gotcha below. Store the key as base64 in APPLE_PRIVATE_KEY and base64_decode() in a config callback, or read it from a file outside VCS in a service provider.

Testing. There's no Socialite::fake() equivalent for custom drivers. Use Http::fake() to stub https://appleid.apple.com/auth/token and the userinfo endpoint, then assert your controller's account-linking behaviour. Unit-test the JWT generation separately (decode with the public key and assert iss/sub/aud/exp).

Account deletion. Apple's App Store rules require token revocation when a user deletes their account. The driver covers login; revocation is a POST https://appleid.apple.com/auth/revoke with client_id, client_secret, token — call it from your deletion listener before removing the identity row.

Gotchas and Tips

The 2.2.2 fix ($headers by reference). The internal request helper previously passed an array literal into a by-reference array &$headers parameter, which raises Argument #2 ($headers) could not be passed by reference / Only variables should be passed by reference depending on PHP version and error handler. 2.2.2 assigns the array to a variable first. This is a patch release with no API change — safe to bump. It only bites you if you vendored, subclassed, or copy-pasted the token-exchange method; if so, sync your override's signature to array &$headers = [] and pass a variable, not a literal.

getName() is null after first login. Not a bug — an Apple privacy feature. Never rely on it for display after registration; always fall back to your stored value.

Don't key users on email. Users can hide their address behind @privaterelay.appleid.com, and users can change/forward relay addresses. The sub claim is stable per team; email is not.

invalid_client is almost always the secret. Causes: wrong key_id/team_id, Service ID used where a Bundle ID (or vice versa) was expected, expired or clock-skewed exp in the JWT (exp must be ≤ 6 months from iat), or server clock drift — keep NTP healthy.

invalid_grant is the authorization code. Codes are single-use and short-lived. Double-submitting the callback (browser refresh, React StrictMode, duplicate AJAX) will fail the second time; guard with an idempotency check.

PEM newlines. APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGT...\n-----END PRIVATE KEY-----" in .env gives literal backslash-n. Either use base64 or str_replace('\n', "\n", $key) before passing it to the provider. A malformed key surfaces as an OpenSSL error, not a helpful Apple message.

Redirect URI mismatch is silent-ish. Apple validates scheme + host + path with no trailing slash tolerance. A http vs https or www vs apex difference yields a generic error page before your callback ever runs. Log the exact URI you send and diff it against the Developer portal.

stateless() without a session throws Laravel\Socialite\Two\InvalidStateException — common when the callback lands on a different domain/subdomain than the redirect started (session cookie scope), or behind a load balancer without sticky sessions.

Debugging checklist: catch InvalidStateException separately from GuzzleHttp\Exception\ClientException; dump the decoded Apple error body (error, error_description) — the driver often surfaces only a generic exception. Enable Guzzle debug middleware temporarily to see the raw token request.

Extension points worth knowing: the SocialiteWasCalled event (register additional Apple-based drivers), re-binding via Socialite::extend('apple', ...) in a later-registered provider (last registration wins), and with(['response_mode' => 'form_post']) if you prefer the POST callback — remember that route then needs Route::post.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager