skip to content

Socialite Social Login

Socialite redirects to an OAuth provider and turns the callback into a provider user you must map to a local account. Interviewers probe state checks, stateless() and email-matching takeovers.

on this pageshow

explore

questions

6

In Laravel, how do you add GitHub sign-in with Socialite, from the config/services.php entry to the callback route?

level: juniorimportance: must knowfreq 55%

answer

  1. three keys under services.github
  2. two routes: out and back
  3. redirect() returns a RedirectResponse
  4. user() swaps the code for a token
  5. Laravel\Socialite\Two\User with getId()

basics

~10 s

Add a github entry with client_id, client_secret and redirect to config/services.php, then define two routes: one returning Socialite::driver('github')->redirect(), and a callback calling ->user() to get the provider user you map to a local account.

solid answer

~40 s

After `composer require laravel/socialite`, add a `github` entry to `config/services.php` with `client_id`, `client_secret` and `redirect`, reading the secrets from `.env`; a missing key throws `DriverMissingConfigurationException`, and a relative `redirect` such as `/auth/github/callback` is turned into a full URL. The first route returns `Socialite::driver('github')->redirect()`, which stores a random `state` in the session and sends the browser to GitHub. The callback route calls `Socialite::driver('github')->user()`: it checks the returned `state`, exchanges the `code` for an access token, fetches the profile and returns a `Laravel\Socialite\Two\User` with `getId()`, `getNickname()`, `getName()`, `getEmail()`, `getAvatar()`, plus `token`, `refreshToken`, `expiresIn` and `approvedScopes`. You then find or create the local user keyed on the provider id and sign them in. Both routes must run with sessions, so they belong in `routes/web.php`.

code

php · 20 lines
php
<?php

use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Laravel\Socialite\Socialite;

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

    $member = User::updateOrCreate(
        ['github_id' => $githubUser->getId()],
        ['name' => $githubUser->getName() ?? $githubUser->getNickname(),
         'email' => $githubUser->getEmail()],
    );

    Auth::login($member);

    return redirect('/feed');
});

go deeper

for a junior

Recall the three services.php keys, the two routes, and that redirect() sends the user away while user() returns a provider user you still have to map and sign in.

for a middle

Explain what redirect() and user() do underneath: state in the session, code for token exchange, profile fetch, and which fields the returned user carries.

for a senior

Point out the failure modes: sessionless routes, mismatched callback hosts, a redirect value that differs from the provider registration, and null emails.

for a principal

Decide which providers the product should offer and whether social login replaces or sits beside password accounts.

## What Socialite is **Laravel Socialite** (`laravel/socialite`) is the first-party package that lets a Laravel app act as an **OAuth client**: it sends a visitor to a provider such as GitHub or Google, receives them back, and turns the provider's answer into a user object. It does not create accounts or sign anyone in; that stays your code. Built-in drivers include `github`, `google`, `gitlab`, `bitbucket`, `facebook`, `linkedin-openid`, `slack`, `slack-openid` and `x`. ## Step 1: configuration For a developer-community site offering "Sign in with GitHub", register an OAuth app in GitHub's developer settings and copy its credentials into `.env`. Then add an entry to `config/services.php` (the skeleton's file has no `github` key yet): ```php 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => '/auth/github/callback', ], ``` - The **config key must match the driver name**: `Socialite::driver('github')` reads `services.github`. - All three keys are **required**. If one is missing, the manager throws `Laravel\Socialite\Exceptions\DriverMissingConfigurationException` naming the missing keys. - A `redirect` value that starts with `/` is passed through `url()` and becomes absolute; it must match the callback URL registered with the provider. - An optional `scopes` array in the same entry is added to the driver's default scopes. ## Step 2: the two routes ```php use Laravel\Socialite\Socialite; Route::get('/auth/github/redirect', fn () => Socialite::driver('github')->redirect()); Route::get('/auth/github/callback', function () { $githubUser = Socialite::driver('github')->user(); // map to a local account, then sign in }); ``` What each call does: 1. `redirect()` generates a random 40-character `state`, stores it in the **session** under `state`, builds GitHub's authorize URL with `client_id`, `redirect_uri`, `scope`, `response_type=code` and `state`, and returns an `Illuminate\Http\RedirectResponse`. 2. The member approves on GitHub and is sent back with `code` and `state` in the query string. 3. `user()` pulls `state` from the session and compares it with the query value; a mismatch throws `Laravel\Socialite\Two\InvalidStateException`. 4. It posts the `code` to GitHub's token endpoint, receives an access token, calls GitHub's user API and maps the result. ## Step 3: the user object | Member | Meaning | |---|---| | `getId()` | the provider's stable user id; key your link on this | | `getNickname()`, `getName()` | GitHub login and display name | | `getEmail()` | may be null; with the default `user:email` scope, the GitHub driver returns the primary **verified** address | | `getAvatar()` | avatar URL | | `token`, `refreshToken`, `expiresIn` | the OAuth tokens from the exchange | | `approvedScopes` | the scopes the provider says it granted | | `getRaw()` / `user` | the raw profile array | ## Step 4: mapping and signing in The callback looks the member up by provider and provider id, creates a record if none exists, and then signs them in with the session guard. Keying on `getId()` rather than on the email matters: emails change and can be missing, while the id is stable. The skeleton's `users` table was designed for password accounts, so social login usually needs schema work first: - a column or separate table for the provider id, with a unique index; - a nullable `password`, because a member who only ever uses GitHub has none; - a nullable `email` if you accept members without a public, verified address; - the new columns added to the model's `#[Fillable]` list, or `updateOrCreate()` silently drops them. ## Adding Google as well A second provider is another `config/services.php` entry (`google`) and usually one pair of routes with a `{provider}` parameter. Validate that parameter against an allow-list such as `['github', 'google']` before calling `Socialite::driver($provider)`: an unknown name makes the manager throw `InvalidArgumentException`, and a configured-but-unintended driver would otherwise be reachable too. ## Common mistakes - Putting the callback in `routes/api.php`, where there is no session: `redirect()` and `user()` then fail with "Session store not set on request." - Registering `http://localhost` with the provider but browsing on `127.0.0.1`, so the callback lands on a different cookie domain and the state is missing. - Registering a callback URL with the provider that differs from the `redirect` value, so the provider refuses the request before the member sees the consent screen. - Assuming `getEmail()` always returns a value.

  • What happens if config/services.php has a github entry without a redirect key?
    `SocialiteManager::buildProvider()` checks for `client_id`, `client_secret` and `redirect`, and throws `DriverMissingConfigurationException` listing the missing keys as soon as `Socialite::driver('github')` is resolved. The error appears on the redirect route, before any request reaches GitHub.
  • Why must both Socialite routes run inside the web middleware group?
    `redirect()` writes the `state` into the session and `user()` reads it back, so both need the session started by the web group. In `routes/api.php` there is no session, and the first `session()` call throws a `RuntimeException` ("Session store not set on request.") instead of a clean OAuth error.

Socialite is like a hotel front desk that checks guests in with a passport issued elsewhere: it sends you to the issuing office, takes the stamped reply you bring back and reads your details off it. Deciding which room you get, and whether you are the same guest as last week, is still the hotel's job.

saying these in an interview costs you the question

  • Socialite creates the local user and logs them in automatically
  • Socialite::driver('github') reads its credentials from config/auth.php
  • getEmail() always returns an address for GitHub users
  • The callback route can live in routes/api.php like any other endpoint
  • Matching members by email is the documented way to key GitHub logins
open as a page

In Laravel Socialite, how do scopes(), setScopes() and with() differ, and why can replacing GitHub's default scope leave getEmail() returning null?

level: middleimportance: should knowfreq 30%

basics

~20 s

scopes() adds to the driver's default scopes, setScopes() replaces them, and with() sets extra query parameters. The GitHub driver fetches the primary verified email only when user:email is requested, so setScopes() without it can leave getEmail() null.

open as a page

What does Laravel Socialite keep in the session between redirect() and user(), why does the callback throw InvalidStateException, and when is stateless() appropriate?

level: middleimportance: should knowfreq 42%

basics

~20 s

redirect() stores a random state (and, with enablePKCE(), a code_verifier) in the session; user() pulls the state and throws InvalidStateException when it is missing or differs. stateless() skips that check for sessionless APIs and gives up its protection.

open as a page

On a Laravel site with GitHub and Google sign-in, why is linking the Socialite user to a local account by email alone dangerous, and what should the callback do instead?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Email-only matching lets anyone who controls an address at a provider, or pre-registered it locally unverified, take over the account. Key links on provider plus getId(); merge by email only when both sides are verified.

open as a page

A mobile app sends your Laravel API a Google token for Socialite's userFromToken(); what does Socialite check about that token, and what does it leave to you?

level: seniorimportance: should knowfreq 28%

basics

~20 s

userFromToken() fetches the profile for a token the client already holds. For a Google ID token, Socialite verifies signature, issuer and audience; an opaque access token just goes to userinfo, with no check of which app it was issued to.

open as a page

With Laravel Socialite, which provider tokens does the callback return, and how do you keep calling the provider's API after expiry?

level: middleimportance: nice to knowfreq 22%

basics

~10 s

The Socialite user carries token, refreshToken, expiresIn and approvedScopes. Store them encrypted if you need provider API access, and when the access token expires call Socialite::driver(...)->refreshToken($refresh), which returns a Laravel\Socialite\Two\Token with fresh values.

open as a page