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 Laravel Package

laravel/socialite

Laravel Socialite offers an expressive, fluent OAuth authentication interface for Laravel, supporting Bitbucket, Facebook, GitHub, GitLab, Google, LinkedIn, Slack, Twitch, and X while handling most social login boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up credentials

composer require laravel/socialite
php artisan vendor:publish --tag=laravel-config   # only if you don't have config/services.php
// config/services.php
'github' => [
    'client_id'     => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect'      => env('GITHUB_REDIRECT_URI'), // absolute URL, must match the app registration exactly
],

SocialiteServiceProvider and the Socialite facade alias are auto-discovered — no manual registration on Laravel 5.8+.

Schema — you need, at minimum, a way to tie a remote identity to a local user:

Schema::table('users', function (Blueprint $table) {
    $table->string('provider')->nullable()->after('password');
    $table->string('provider_id')->nullable()->after('provider');
    $table->string('provider_token')->nullable()->after('provider_id');
    $table->string('provider_refresh_token')->nullable()->after('provider_token');
    $table->unique(['provider', 'provider_id']);
});

First use case — GitHub login in two routes

// routes/web.php  (must be in the `web` group for session/state)
Route::get('/auth/{provider}/redirect', [SocialAuthController::class, 'redirect']);
Route::get('/auth/{provider}/callback', [SocialAuthController::class, 'callback']);
public function redirect(string $provider)
{
    return Socialite::driver($provider)->redirect();
}

public function callback(string $provider)
{
    $remote = Socialite::driver($provider)->user();

    $user = User::updateOrCreate(
        ['provider' => $provider, 'provider_id' => $remote->getId()],
        [
            'name'              => $remote->getName() ?? $remote->getNickname(),
            'email'             => $remote->getEmail(),
            'provider_token'    => $remote->token,
            'provider_refresh_token' => $remote->refreshToken,
        ]
    );

    Auth::login($user, remember: true);

    return redirect()->intended('/dashboard');
}

Where to look first

  1. The official docs page linked from the README (laravel.com/docs/socialite) — the README itself is a stub.
  2. src/Two/AbstractProvider.php — this is where redirect(), user(), scopes(), with(), stateless(), and state validation actually live. Read it once; it explains 90% of the surprises.
  3. src/Two/User.php and src/Contracts/User.php — the exact accessor set (getId, getNickname, getName, getEmail, getAvatar, getRaw, token, refreshToken, expiresIn, getApprovedScopes).
  4. src/Two/GithubProvider.php etc. — each provider is ~40 lines and tells you which scopes are requested by default and how the raw payload is mapped.

Implementation Patterns

Multi-provider controller with an allowlist

Never pass a user-controlled string straight into Socialite::driver().

private const PROVIDERS = ['github', 'google', 'gitlab', 'linkedin-openid', 'slack'];

public function redirect(string $provider)
{
    abort_unless(in_array($provider, self::PROVIDERS, true), 404);

    // Persist intended destination before leaving the app
    session()->put('auth.intended', url()->previous());

    return Socialite::driver($provider)->redirect();
}

Scopes and extra authorization params

return Socialite::driver('google')
    ->scopes(['https://www.googleapis.com/auth/calendar.readonly']) // ADDS to defaults
    ->with(['access_type' => 'offline', 'prompt' => 'consent'])     // required for refresh tokens
    ->redirect();

scopes() merges with the provider defaults; setScopes() replaces them. Use setScopes() only when you know you don't want the defaults (and then remember to re-add email/openid where needed).

Storing tokens for later API calls

Socialite's job ends at the user object; ongoing API access is yours to manage.

$user->forceFill([
    'provider_token'         => encrypt($remote->token),
    'provider_refresh_token' => $remote->refreshToken ? encrypt($remote->refreshToken) : null,
    'provider_expires_at'    => $remote->expiresIn ? now()->addSeconds($remote->expiresIn) : null,
])->save();

Then call the provider yourself (Socialite will not refresh tokens for you):

Http::withToken(decrypt($user->provider_token))
    ->get('https://api.github.com/user/repos');

Stateless mode for APIs / mobile clients

When the flow is driven by a SPA or native app (no shared session across the redirect), stateless() skips the session-based state check:

return Socialite::driver('google')->stateless()->with(['redirect_uri' => $request->redirect_uri])->redirect();

Do this deliberately — you lose CSRF protection on the callback and must substitute your own signed state/nonce if the client can be spoofed.

PKCE (opt-in)

return Socialite::driver('provider')->enablePKCE()->redirect();

Available since 5.2.0 and public since 5.2.5. The verifier is stored in the session, so PKCE is incompatible with stateless().

Custom / community providers

Core is explicitly not accepting new adapters. Two escape hatches:

// 1. Community packages (Apple, Discord, Steam, Twitch variants, ...)
// composer require socialiteproviders/apple  -> registers via its own listener

// 2. Internal SSO or a niche service — extend the manager
public function boot(): void
{
    Socialite::extend('acme', fn ($app) => Socialite::buildProvider(
        AcmeProvider::class,
        config('services.acme')
    ));
}

Provider classes extend Laravel\Socialite\Two\AbstractProvider (OAuth2) or One\AbstractProvider (OAuth1).

Testing

use Laravel\Socialite\Facades\Socialite;

Socialite::fake('github');           // v5.24+
// then the driver forwards to the real provider but you stub the user

The fakes were significantly improved in v5.28 (direct user faking); check composer show laravel/socialite and mirror the driver key your controller actually uses. For anything the fake doesn't cover, mock the manager:

Socialite::shouldReceive('driver->user')->andReturn(
    (new \Laravel\Socialite\Two\User)->map([
        'id' => '123', 'name' => 'Test', 'email' => '[email protected]',
    ])
);

Account linking instead of account creation

// Logged-in user connecting a provider
$remote = Socialite::driver('github')->user();

$request->user()->forceFill([
    'provider'    => 'github',
    'provider_id' => $remote->getId(),
])->save();

Guard against the same remote identity already being linked to a different local user — the composite unique index throws a QueryException you should catch and surface as "this account is already linked".


Gotchas and Tips

The redirect URI must match byte-for-byte. http://localhost:8000/auth/github/callback and http://127.0.0.1:8000/auth/github/callback are different registrations. Trailing slashes, ports, and https all matter. This is the #1 cause of redirect_uri_mismatch.

Config cache bites you after .env edits. php artisan config:cache during deploy freezes services.*; rotating a client secret requires a re-cache. Locally, php artisan config:clear is the reflex fix when credentials "suddenly don't work".

Call user() once and reuse it. The authorization code is single-use; a second user() call in the same request can hit the provider again and fail. Assign to a variable immediately and pass it around. If you need the user in another layer, don't re-resolve the driver.

State validation failures (InvalidStateException) come from a missing/mismatched session state — usually because the callback route is outside the web middleware group, the session cookie was dropped (cross-domain redirect, SESSION_SAME_SITE=strict with a cross-site POST), or APP_KEY changed between redirect and callback. Since v5.26.1 the comparison uses hash_equals, so don't rely on any prior loose-comparison behaviour.

Handle user denial before calling user(). A cancelled consent returns to your callback with ?error=access_denied and no code — user() will throw. Check $request->has('error') first and route to a friendly page.

Email is frequently null or unverified.

  • GitHub needs the user:email scope and may still return null for private emails; also check /user/emails for a verified primary.
  • Facebook users can hide email; X/Twitter requires the email scope and a confirmed email — v5.24.1 had to remove a redundant email mapping because unconfirmed accounts errored.
  • With Sign in with Apple you'll often get a private relay address, and name arrives only on the very first authorization.

Never identify users by email alone; always key on provider + provider_id, and treat a null email as a valid state (ask the user to supply one).

stateless() and PKCE are mutually exclusive — the verifier lives in the session. Also remember stateless() removes the CSRF guarantee; if the callback is reachable from a browser, add your own state check.

Http::fake() typically won't intercept Socialite. Providers talk to the network through Socialite's own Guzzle client, not Laravel's HTTP client, so faking HTTP doesn't help. Use Socialite::fake() or mock the manager; otherwise your test suite makes real outbound calls (and will be slow/flaky/CI-blocked).

Socialite does not refresh tokens. expiresIn and refreshToken are handed to you as data. Refresh grants, revocation, and re-auth prompts ("your GitHub connection expired, reconnect") are application code.

Scope changes require re-consent. Adding a scope silently doesn't apply to existing users' stored tokens; getApprovedScopes() (5.5+) lets you detect the gap and send them through redirect() again.

Provider quirks worth pinning:

  • Twitter/X — OAuth version is selected in config ('oauth' => 1|2). The OAuth1 driver has its own scope handling and different response shape from OAuth2.
  • Facebook — the Graph API version is bump-able via config; core moved to v23.0 in 5.23.0. Pin it explicitly so a composer update doesn't silently change the payload shape.
  • LinkedIn — the legacy linkedin driver and linkedin-openid (OIDC, added 5.9.0) target different endpoints. Pick based on which product your LinkedIn app is registered for; the legacy provider has had recurring undefined array key fixes.
  • GitLab — self-hosted instances are supported (5.1.3+) by pointing the driver at your instance URL rather than gitlab.com.
  • Slack — v2 API with bot-token support; getRaw() differs noticeably from user-token responses.

Octane and long-lived workers. Driver instances are cached on the manager; 5.2.3 added reset methods so Octane doesn't leak config between requests. If you mutate config at runtime (config()->set(...) per tenant), verify the driver you get back reflects it — 5.6.2 specifically fixed stale config under Octane.

Encrypt stored tokens. They are bearer credentials with the same blast radius as passwords. encrypt() on write, decrypt() on read, and keep them out of $fillable / API resources / logs.

Route caching. Keep the redirect/callback routes on controller methods, not closures, or php artisan route:cache will fail in production.

Version notes for upgrade planning: 5.23.2 adds PHP 8.5 compatibility, 5.24.3 adds Laravel 13 support, and 5.30.1 moved to phpseclib v4. Pin ^5.24 at minimum if you want the testing fakes; ^5.30 if you want the OIDC nonce validation fix for Facebook.

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.
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections
webx-ui/module-catalog-feeds
makidizajnerica/laravel-dto
matviib/scheduler
webx-ui/templates
anodyne/blade-tabler-icons
bref/laravel-websockets
anodyne/blade-hugeicons
1994/flatpack-statamic