skip to content

Password Reset & Verification

The Password broker issues and checks reset tokens, and MustVerifyEmail with the verified middleware holds users behind a signed email link. Interviewers probe expiry, throttling and enumeration.

on this pageshow

explore

questions

6

In Laravel, what must be in place for the verified middleware to keep users with unconfirmed email addresses out of a route?

level: juniorimportance: must knowfreq 50%

answer

  1. an interface, not just a trait
  2. email_verified_at stays null
  3. Registered event triggers the mail
  4. three named verification routes
  5. auth first, then verified

basics

~10 s

App\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 s

The `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
<?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

for a junior

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.

for a middle

Explain the interface-versus-trait trap, what EnsureEmailIsVerified does for HTML and JSON requests, and why auth must come before verified.

for a senior

Diagnose silent failures: routes guarded by verified that let everyone in, missing first emails, and redirect loops for guests.

for a principal

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
open as a page

In Laravel, how does the Password broker carry a reset from Password::sendResetLink to Password::reset, and what does your own code still have to do?

level: middleimportance: must knowfreq 58%

basics

~20 s

Password::sendResetLink finds the user by email, stores a hashed token and mails a link. Password::reset re-checks email and token, runs your closure to save the new password, deletes the token, and both return a status string.

open as a page

In a Laravel API behind a separate SPA, how do you make reset and verification emails link to the front end instead of Laravel routes?

level: middleimportance: should knowfreq 30%

basics

~10 s

Register URL builders in AppServiceProvider::boot(): ResetPassword::createUrlUsing(fn ($user, $token) => ...) returns a front-end URL with the token and email, and VerifyEmail::createUrlUsing(fn ($notifiable) => ...) must build and embed the signed verification URL itself.

open as a page

In Laravel's config/auth.php, what do a password broker's expire and throttle settings do, and how do the database and cache token drivers differ?

level: middleimportance: should knowfreq 38%

basics

~20 s

expire is how many minutes a reset token stays valid; throttle is how many seconds before the same account gets another link (both 60 in the skeleton). Database-driver rows need auth:clear-resets; cache-driver entries expire by TTL.

open as a page

A Laravel forgot-password endpoint copied from the documented example reveals which email addresses have accounts - where does it leak, and how do you close it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The broker returns passwords.user for unknown emails but passwords.sent or passwords.throttled for real ones, and the handler shows different messages. Show one neutral message, move slow mail sending out of the request so the 200 ms timebox can hide it, and rate-limit the route.

open as a page

In Laravel, what does EmailVerificationRequest check when a user opens the verification link, and what must happen when a verified user changes their email address?

level: seniorimportance: should knowfreq 32%

basics

~20 s

EmailVerificationRequest authorizes only if the route's id equals the signed-in user's key and its hash equals sha1 of that user's current email; fulfill() then sets email_verified_at and fires Verified. On an email change, call markEmailAsUnverified() and resend the link.

open as a page