In Laravel, what must be in place for the verified middleware to keep users with unconfirmed email addresses out of a route?
answer
- an interface, not just a trait
- email_verified_at stays null
- Registered event triggers the mail
- three named verification routes
- auth first, then verified
basics
~10 sApp\Models\User must implement Illuminate\Contracts\Auth\MustVerifyEmail, users needs an email_verified_at column, sign-up must dispatch Registered, the verification.notice, verification.verify and verification.send routes must exist, and routes use ['auth', 'verified'].
solid answer
~40 sThe `verified` alias maps to `EnsureEmailIsVerified`, which only blocks a user whose model **implements** `Illuminate\Contracts\Auth\MustVerifyEmail` and whose `email_verified_at` is null. The skeleton's `User` has that `use` line commented out, and the base `Illuminate\Foundation\Auth\User` already includes the `MustVerifyEmail` trait, so the methods exist while the check silently lets everyone through until you add `implements MustVerifyEmail`. The default users migration already has a nullable `email_verified_at`. Registration must dispatch `Illuminate\Auth\Events\Registered`; Laravel auto-registers `SendEmailVerificationNotification` for it. You define three routes: `verification.notice` (the "check your inbox" page, where unverified users are redirected), `verification.verify` (the signed link, with `auth` and `signed`), and `verification.send` (resend, throttled). Then protect routes with `['auth', 'verified']`; JSON requests get a 403 instead of a redirect.
code
php · 8 lines<?php
use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/courses/{course}/lessons', [LessonController::class, 'index']);
Route::post('/courses/{course}/enrol', [EnrolmentController::class, 'store']);
});go deeper
Recall the pieces: implement MustVerifyEmail on User, the email_verified_at column, the Registered event, the three named routes, and ['auth', 'verified'] on protected routes.
Explain the interface-versus-trait trap, what EnsureEmailIsVerified does for HTML and JSON requests, and why auth must come before verified.
Diagnose silent failures: routes guarded by verified that let everyone in, missing first emails, and redirect loops for guests.
Decide which features truly need a verified address on your product and how long an unverified account may live before it is cleaned up.
## What the `verified` middleware checks `verified` is one of the middleware aliases Laravel registers for you; it points at `Illuminate\Auth\Middleware\EnsureEmailIsVerified`. Its whole decision is: - if there is **no authenticated user**, or - the user **implements** the `Illuminate\Contracts\Auth\MustVerifyEmail` interface **and** `hasVerifiedEmail()` returns false, then it refuses the request. HTML requests are redirected to the route named `verification.notice` (or another route you pass as `verified:route.name`); requests that expect JSON get `abort(403, 'Your email address is not verified.')`. Everyone else passes. ## The interface trap The framework's base class `Illuminate\Foundation\Auth\User` already **uses** the `Illuminate\Auth\MustVerifyEmail` **trait**, which provides `hasVerifiedEmail()`, `markEmailAsVerified()`, `markEmailAsUnverified()`, `sendEmailVerificationNotification()` and `getEmailForVerification()`. It does **not** implement the **interface**. In the Laravel 13 skeleton, `app/Models/User.php` even has `// use Illuminate\Contracts\Auth\MustVerifyEmail;` commented out. So an e-learning site that adds `verified` to its course routes without touching the model gets no protection at all: the `instanceof` check fails and every signed-in student passes. The fix is one line: ```php use Illuminate\Contracts\Auth\MustVerifyEmail; class User extends Authenticatable implements MustVerifyEmail { // ... } ``` ## The checklist 1. **Model**: `implements MustVerifyEmail` on `App\Models\User`. 2. **Column**: `users.email_verified_at`, nullable; the skeleton's first migration already creates it and the model casts it to `datetime`. `hasVerifiedEmail()` is simply `! is_null($this->email_verified_at)`. 3. **Sending the first email**: dispatch `event(new Registered($user))` after sign-up. Laravel's event service provider listens for `Registered` with `SendEmailVerificationNotification` automatically, and that listener sends the email only when the user implements the interface and is not yet verified. 4. **Routes**, with these exact names: - `verification.notice`: `GET /email/verify`, behind `auth`, showing "check your inbox". - `verification.verify`: `GET /email/verify/{id}/{hash}`, behind `auth` and `signed`, type-hinting `EmailVerificationRequest` and calling `$request->fulfill()`. - `verification.send`: `POST /email/verification-notification`, behind `auth` and a throttle such as `throttle:6,1`, calling `$request->user()->sendEmailVerificationNotification()`. 5. **Protection**: `->middleware(['auth', 'verified'])` on everything that needs a confirmed address. ## Why `auth` comes first `EnsureEmailIsVerified` treats a guest as unverified and redirects to `verification.notice`, which itself needs `auth`, so the guest is bounced twice. Pairing `auth` before `verified` sends guests straight to the login page and gives `verified` a user to inspect. ## The three routes in code ```php Route::get('/email/verify', fn () => view('auth.verify-email')) ->middleware('auth')->name('verification.notice'); Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) { $request->fulfill(); return redirect('/courses'); })->middleware(['auth', 'signed'])->name('verification.verify'); Route::post('/email/verification-notification', function (Request $request) { $request->user()->sendEmailVerificationNotification(); return back()->with('message', 'Verification link sent!'); })->middleware(['auth', 'throttle:6,1'])->name('verification.send'); ``` The names matter more than the paths: `EnsureEmailIsVerified` redirects to `verification.notice` by name, and the stock `VerifyEmail` notification signs a URL for the route named `verification.verify`. Rename either and the flow breaks with a missing-route error at the moment a student first hits it. The resend route is throttled because each call sends a real email; without a limit, a signed-in account can be used to flood its own inbox or your mail quota. ## Summary table | Piece | Provided by the skeleton? | What happens if missing | |---|---|---| | `MustVerifyEmail` trait methods | yes, via the base `User` | nothing to call | | `implements MustVerifyEmail` | no, commented out | `verified` lets everyone in, no email sent | | `email_verified_at` column | yes | users never count as verified; `markEmailAsVerified()` fails on save | | `Registered` dispatched | only by starter kits or your code | no first email | | the three named routes | no | `Route [verification.notice] not defined.` | Starter kits built on Fortify wire most of this for you, but interviewers ask for the manual version to see whether you know which piece does what.
- What does the verified middleware return to a JSON client whose email is unverified?When `$request->expectsJson()` is true, `EnsureEmailIsVerified` calls `abort(403, 'Your email address is not verified.')` instead of redirecting. An SPA or mobile client should treat that 403 as "show the verify-your-email screen" rather than as a permissions error.
- Your registration code creates the user and logs them in, but no verification email arrives; what is the likely cause?Either the model does not implement the `MustVerifyEmail` interface, so the auto-registered `SendEmailVerificationNotification` listener skips the user, or the code never dispatches `Illuminate\Auth\Events\Registered`. The listener only runs on that event and only for unverified users of the interface.
saying these in an interview costs you the question
- Extending Illuminate\Foundation\Auth\User already makes the verified middleware work
- You must register the SendEmailVerificationNotification listener yourself
- verified checks a boolean is_verified column on the users table
- The verified middleware sends guests straight to the login page
- The verification email is sent whenever a user row is created