laravel/framework · error · HttpException

Your email address is not verified.

Error message

Your email address is not verified.

What it means

Produced by EnsureEmailIsVerified middleware via abort(403, 'Your email address is not verified.') — only when the request expects JSON. For non-JSON requests it redirects to the verification.notice route (or a custom $redirectToRoute). The 403 fires when there is no authenticated user OR the user implements MustVerifyEmail and has not verified their email. This is an HTTP-level access-control response, not a thrown exception a caller catches in normal PHP flow.

Source

Thrown at src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php:37

    {
        return static::class.':'.$route;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $redirectToRoute
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse|null
     */
    public function handle($request, Closure $next, $redirectToRoute = null)
    {
        if (! $request->user() ||
            ($request->user() instanceof MustVerifyEmail &&
            ! $request->user()->hasVerifiedEmail())) {
            return $request->expectsJson()
                ? abort(403, 'Your email address is not verified.')
                : Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
        }

        return $next($request);
    }
}

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Trigger email verification: send the verification link via the verification.send route (Event dispatch EmailVerificationRequested).
  2. Mark the user verified manually in tests/dev: $user->markEmailAsVerified();.
  3. Ensure the route is also behind the 'auth' middleware so $request->user() is non-null.
  4. For APIs, return a structured JSON error/redirect URL instead of bare abort by customizing the middleware or adding an after-handler.
  5. Exclude the route from the verified middleware if it must be publicly accessible.

Example fix

// before — routes/web.php
Route::get('/api/profile', fn () => ...)->middleware(['auth', 'verified']);

// after — trigger verification + ensure user is set
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;

Route::get('/email/verify', fn () => view('auth.verify'))->name('verification.notice');
Route::post('/email/verification-notification', function (\Illuminate\Http\Request $request) {
    $request->user()->sendEmailVerificationNotification();
    return back()->with('status', 'verification-link-sent');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');

// In tests, verify the user first:
$user = User::factory()->create(['email_verified_at' => now()]);
$this->actingAs($user);
Defensive patterns

Strategy: validation

Validate before calling

// In a controller/middleware, verify before relying on 'verified':
$user = $request->user();
if (! $user || ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())) {
    return response()->json(['message' => 'Email verification required.', 'verify_url' => route('verification.notice')], 403);
}

Type guard

use Illuminate\Contracts\Auth\MustVerifyEmail;

function emailVerifiedForRequest(\Illuminate\Http\Request $request): bool {
    $user = $request->user();
    return $user !== null
        && (! $user instanceof MustVerifyEmail || $user->hasVerifiedEmail());
}

Try / catch

// abort(403) is an HttpResponseException; you cannot catch it as a normal exception in the
// controller that hits the middleware. Catch globally in an API exception handler for structured JSON:
// app/Exceptions/Handler.php
public function register(): void {
    $this->renderable(function (\Symfony\Component\HttpKernel\Exception\HttpException $e, $request) {
        if ($e->getStatusCode() === 403 && $e->getMessage() === 'Your email address is not verified.' && $request->expectsJson()) {
            return response()->json([
                'message' => 'Email not verified.',
                'action'  => 'verify_email',
                'resend_url' => route('verification.send'),
            ], 403);
        }
    });
}

Prevention

When it happens

Trigger: Applying the 'verified' middleware aliasus (EnsureEmailIsVerified) to a route and hitting it via an API client that sets Accept: application/json while the user is unverified (or not logged in). Common with SPA/mobile clients that always send Accept JSON.

Common situations: Forgetting to send the email verification after registration. Users who registered but never clicked the verification link. Token/session expired so $request->user() is null. SPA defaults to JSON and never sees the redirect, getting a raw 403 instead.


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/c26565ab26390f5c.json. Report an issue: GitHub.