<?php
namespace App\Models;
use App\Notifications\VerifyEmailNotification;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements MustVerifyEmail
{
use Notifiable;
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
public function hasVerifiedEmail()
{
return ! is_null($this->email_verified_at);
}
public function markEmailAsVerified()
{
return $this->forceFill([
'email_verified_at' => $this->freshTimestamp(),
])->save();
}
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmailNotification());
}
}
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
class VerifyEmailNotification extends Notification
{
use Queueable;
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
$url = $this->verificationUrl($notifiable);
return (new MailMessage())
->subject('Verify Your Email Address')
->line('Please confirm your email by clicking the button below.')
->action('Verify Email Address', $url)
->line('This link will expire shortly for your security.');
}
protected function verificationUrl($notifiable)
{
$minutes = Config::get('auth.verification.expire', 60);
return URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes($minutes),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
}
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
class EmailVerificationController extends Controller
{
public function notice()
{
return view('auth.verify-email');
}
public function verify(Request $request, string $id, string $hash)
{
$user = User::findOrFail($id);
if (! hash_equals($hash, sha1($user->getEmailForVerification()))) {
abort(403, 'Invalid verification link.');
}
if ($user->hasVerifiedEmail()) {
return redirect()->route('dashboard')->with('status', 'already-verified');
}
if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
return redirect()->route('dashboard')->with('status', 'email-verified');
}
public function resend(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->route('dashboard');
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}
<?php
use App\Http\Controllers\Auth\EmailVerificationController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth')->group(function () {
Route::get('/email/verify', [EmailVerificationController::class, 'notice'])
->name('verification.notice');
Route::get('/email/verify/{id}/{hash}', [EmailVerificationController::class, 'verify'])
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('/email/verification-notification', [EmailVerificationController::class, 'resend'])
->middleware('throttle:6,1')
->name('verification.send');
});
This snippet shows how Laravel implements a self-contained email-verification flow using its built-in signed URL machinery instead of storing a separate verification token. The core idea is that a signed URL carries a cryptographic signature query parameter derived from the route parameters and the application key, so a link can be validated later without persisting anything server-side. A temporary signed URL adds an expires timestamp to that signature, giving the link a natural expiry that cannot be tampered with.
In User model, the class implements the framework's MustVerifyEmail contract via the MustVerifyEmail trait, which supplies hasVerifiedEmail, markEmailAsVerified, and sendEmailVerificationNotification. The last method is overridden to dispatch the custom VerifyEmailNotification rather than the default one, letting the notification control the link's appearance and lifetime.
In VerifyEmailNotification, verificationUrl calls URL::temporarySignedRoute with the route name verification.verify, an expiry drawn from config, and two parameters: the user id and a hash computed as sha1 of the email address. Hashing the email means the link stays valid only while the address is unchanged — if the user changes their email before clicking, the hash no longer matches and verification is refused. The signed route embeds both expires and signature, so any modification to the id, hash, or timestamp invalidates the link.
In EmailVerificationController, the verify method is guarded by the signed middleware, which rejects requests whose signature does not validate before the controller even runs. Inside, it compares the route's hash against a freshly computed hash of the current email using hash_equals to avoid timing leaks, guards against already-verified users, and calls markEmailAsVerified, firing the Verified event. The notice and resend methods round out the flow for prompting and re-sending.
In routes/web.php, the routes wire these pieces together: verification.verify carries the signed and throttle middleware, while verification.send is throttled separately. The trade-off of this approach is statelessness — no token table to clean up — at the cost of links breaking if the app key rotates. It is the idiomatic choice whenever verification state can be recomputed from existing user data.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
Django URL namespacing and reverse lookups
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
Share this code
Here's the card — post it anywhere.