skip to content

Default Global Stack

Laravel ships a default global stack and web group: proxy and host trust, size and string clean-up, cookie encryption, sessions and route binding. Interviewers ask what each one is for.

on this pageshow

explore

questions

6

In Laravel 13, which middleware does the web group run that the api group does not, and why does an API route have no session?

level: juniorimportance: must knowfreq 64%

answer

  1. cookies, session, errors, forgery check, bindings
  2. api group: SubstituteBindings only
  3. EncryptCookies before StartSession
  4. PreventRequestForgery, formerly VerifyCsrfToken
  5. statefulApi() and throttleApi() are opt-in

basics

~10 s

The web group adds EncryptCookies, AddQueuedCookiesToResponse, StartSession, ShareErrorsFromSession and PreventRequestForgery before SubstituteBindings; the api group has only SubstituteBindings. API routes therefore never start a session, decrypt cookies or check CSRF tokens.

solid answer

~40 s

In Laravel 13 the `web` group, applied to `routes/web.php`, runs `EncryptCookies`, `AddQueuedCookiesToResponse`, `StartSession`, `ShareErrorsFromSession`, `PreventRequestForgery` (the CSRF middleware, renamed from `VerifyCsrfToken` in 13) and `SubstituteBindings`. The `api` group, applied to `routes/api.php` with the `/api` prefix, contains only `SubstituteBindings` unless you opt into Sanctum's stateful middleware with `statefulApi()` or rate limiting with `throttleApi()`. So an API route has no session because `StartSession` never runs for it: `$request->session()` throws, and cookies arrive still encrypted. That is deliberate, since API clients authenticate with tokens rather than cookies. Both groups sit inside the same global stack. The order in `web` matters: cookies are decrypted before `StartSession` reads the session ID cookie, and the session exists before errors are shared or the CSRF token is checked.

code

php · 12 lines
php
<?php

use App\Http\Controllers\BallotController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

// routes/api.php - wrapped in the api group: no session, no cookie decryption, no CSRF
Route::post('/polls/{poll}/votes', [BallotController::class, 'store']);

Route::get('/debug-session', function (Request $request) {
    return $request->hasSession() ? 'session' : 'no session';   // returns 'no session'
});

go deeper

for a junior

Recall the six web-group entries in order and that the api group holds only SubstituteBindings by default.

for a middle

Explain why each web entry depends on the previous one, and what is missing on an API route: session, cookie decryption and CSRF checks.

for a senior

Judge when an endpoint belongs in web or api, and recognise that moving a cookie-authenticated form to api.php silently drops CSRF protection.

for a principal

Decide the authentication model per client type, tokens for API consumers and cookie sessions for first-party pages, and keep route files aligned with it.

## Two groups, one global stack Every request to a Laravel 13 application passes through the **global middleware stack** first: path-encoding validation, deferred callbacks, proxy trust, CORS, maintenance mode, post-size check, string trimming and empty-string conversion. After the router matches a route, the route's **middleware group** runs. `withRouting()` in `bootstrap/app.php` attaches: - the `web` group to `routes/web.php`, - the `api` group to `routes/api.php`, together with the `/api` URI prefix, once you have run `php artisan install:api`. ## What the web group contains In order, as defined in `Illuminate\Foundation\Configuration\Middleware`: | # | Middleware | Job | |---|---|---| | 1 | `Illuminate\Cookie\Middleware\EncryptCookies` | Decrypts incoming cookies, encrypts outgoing ones | | 2 | `Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse` | Attaches cookies queued with `Cookie::queue()` to the response | | 3 | `Illuminate\Session\Middleware\StartSession` | Loads the session from its store, saves it after the response | | 4 | `Illuminate\View\Middleware\ShareErrorsFromSession` | Shares the validation error bag with every view | | 5 | `Illuminate\Foundation\Http\Middleware\PreventRequestForgery` | CSRF protection for state-changing requests | | 6 | `Illuminate\Routing\Middleware\SubstituteBindings` | Resolves route parameters into models or enums | If you call `$middleware->authenticateSessions()`, `auth.session` is appended as a seventh entry. ## What the api group contains By default, only `SubstituteBindings`. Two configurators add optional entries at the front: - `$middleware->statefulApi()` prepends Sanctum's `EnsureFrontendRequestsAreStateful`, which lets a first-party SPA use cookie sessions on API routes. - `$middleware->throttleApi()` adds `throttle:api`. ## Why the order inside web matters Each entry depends on the one before it: 1. `StartSession` finds the session by reading the session ID from a cookie. That cookie is encrypted, so `EncryptCookies` must run first to decrypt it. 2. `AddQueuedCookiesToResponse` sits inside `EncryptCookies`, so on the way out the queued cookies are added before the outer layer encrypts them. 3. `ShareErrorsFromSession` and `PreventRequestForgery` both read from the session, so they come after `StartSession`. 4. `SubstituteBindings` comes last, so the checks above run before database queries for bound models. ## Why an API route has no session Because `StartSession` is not in the `api` group, nothing loads a session for `routes/api.php`. Concretely, on an API route in a voting app: - `$request->session()` throws a `RuntimeException` because no session store is set on the request. - `session('poll_draft')` cannot see anything written by a web page. - Cookies are not decrypted, so `$request->cookie('laravel_session')` holds ciphertext. - No CSRF token is checked, which is correct for token-authenticated clients and dangerous if you move a cookie-authenticated form endpoint into `api.php` by mistake. This is a design choice, not an omission: API clients such as mobile apps authenticate per request with a token, so a server-side session adds cost without benefit. A first-party SPA that wants cookie sessions on API routes uses Sanctum's `statefulApi()` rather than adding `StartSession` by hand. ## Seeing it for a real route `php artisan route:list -v` prints each route's route-level middleware, with groups expanded. For a poll page from `routes/web.php` you will see the six web entries; for `POST api/polls/{poll}/votes` only `SubstituteBindings`. Global middleware does not appear there, because it runs before routing. ## Common mistakes - Adding `StartSession` to one API route to "fix" a session error, without `EncryptCookies`, so the session ID cookie is never decrypted and each request gets a fresh session. - Calling `session()->flash()` in an API controller and expecting a later web page to show it. - Assuming `SubstituteBindings` is web-only; both groups include it, so route model binding works on API routes too. ## Version notes that trip candidates - **Laravel 13 renamed the CSRF middleware** from `VerifyCsrfToken` to `PreventRequestForgery`. The old names survive only as deprecated aliases; code that excludes CSRF should name the new class. - Since Laravel 11 there is no `app/Http/Kernel.php` listing `$middlewareGroups`; the defaults live in the framework's `Configuration\Middleware` class and are adjusted from `bootstrap/app.php`. - Since Laravel 11, `routes/api.php` does not exist until you run `php artisan install:api`.

  • Why must EncryptCookies run before StartSession in the Laravel web group?
    `StartSession` looks up the session by the ID stored in the session cookie. Laravel encrypts that cookie, so until `EncryptCookies` has decrypted incoming cookies the ID is ciphertext and no session would be found. Reversing them would start a fresh, empty session on every request.
  • A Laravel 13 test disables CSRF with withoutMiddleware(VerifyCsrfToken::class); why should it be updated?
    In Laravel 13 the web group uses `PreventRequestForgery`. `VerifyCsrfToken` remains only as a deprecated alias, and the upgrade guide tells you to reference `PreventRequestForgery` directly, especially when excluding the middleware in tests or route definitions.

saying these in an interview costs you the question

  • Expects $request->session() to work on routes/api.php by default
  • Names VerifyCsrfToken as the current CSRF class in Laravel 13
  • Believes the api group throttles requests without any configuration
  • Thinks the web and api groups replace the global stack
  • Moves a cookie-authenticated form endpoint into api.php to avoid CSRF errors
open as a page

In Laravel, what do the global TrimStrings and ConvertEmptyStringsToNull middleware do to request input, and how do you exclude a field or request?

level: middleimportance: must knowfreq 56%

basics

~10 s

TrimStrings strips leading and trailing whitespace from every string input except password fields; ConvertEmptyStringsToNull then turns '' into null. Exclude keys with $middleware->trimStrings(except: [...]), and skip whole requests with closures passed to either configurator.

open as a page

A Laravel voting app behind a load balancer logs the balancer's address as every voter's IP and builds http:// links; how do you fix it with trustProxies()?

level: seniorimportance: must knowfreq 50%

basics

~10 s

Laravel trusts no proxy by default, so $request->ip() and the scheme come from the balancer's connection. Configure $middleware->trustProxies(at: [...]) in bootstrap/app.php with the balancer's addresses or CIDR range so TrustProxies honours its X-Forwarded-* headers.

open as a page

In a Laravel app, why does $request->cookie() return null for a cookie set by front-end JavaScript, and how do you fix it?

level: middleimportance: should knowfreq 40%

basics

~20 s

EncryptCookies in the web group decrypts every incoming cookie and sets any value that fails decryption to null, so a plain cookie written by JavaScript reads as null. List it in $middleware->encryptCookies(except: ['voter_theme']) in bootstrap/app.php.

open as a page

In Laravel 13's bootstrap/app.php, how do you remove or replace a default middleware, and what can go wrong when you redefine the global stack with use()?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Use $middleware->remove(Class::class) or replace(Old::class, New::class) for the global stack, and web(remove: [...], replace: [...]) or api(...) for the groups. use([...]) replaces the entire global list, so anything you omit, including trustHosts(), silently stops running.

open as a page

In Laravel, why does an oversized upload fail with a 413 PostTooLargeException before any validation rule runs?

level: middleimportance: nice to knowfreq 24%

basics

~10 s

The global ValidatePostSize middleware compares the request's Content-Length with PHP's post_max_size and throws PostTooLargeException, an HTTP 413, when it is larger. It runs before routing, so validation rules such as max never execute.

open as a page