This plan outlines the steps for an AI to implement a passwordless "magic link" authentication system in a Laravel application, replacing traditional password-based login.
- Create
LoginTokenMigration:- Table name:
login_tokens. - Fields:
string('email')->index(): The user's email address.string('token', 64)->index(): A SHA-256 hash of the random token.timestamp('consumed_at')->nullable(): To ensure single-use.timestamp('expires_at'): Set to 30 minutes from creation.timestamps(): Standard Laravel timestamps.
- Table name:
- Create
LoginTokenModel:- Add
$fillableforemail,token,consumed_at, andexpires_at. - Cast
consumed_atandexpires_attodatetime.
- Add
- Run Migration: Execute
php artisan migrate.
- Create
PasswordlessLoginNotification:php artisan make:notification PasswordlessLoginNotification.- Constructor should accept the
$url. toMailmethod should return a mail message with a "Login" button pointing to the magic link URL.- Mail message should contain wording "If you did not create a login request, please ignore this email.".
- Update Login Form:
- UI: Remove the password field from the login blade.
- Validation: Validate email format only. Do NOT use
exists:users,emailto prevent enumeration. - Rate Limiting: Implement a throttle (e.g., 5 attempts per 10 minutes) on the email/IP.
- Invalidation: Always delete old tokens for the email before generating a new one.
- Token Generation:
- Create a random 40-character token using
Str::random(40). - Store a SHA-256 hash of the token (
hash('sha256', $token)) inlogin_tokens.
- Create a random 40-character token using
- URL Generation:
- Generate a temporary signed route to the confirmation page:
URL::temporarySignedRoute('passwordless.confirm', now()->addMinutes(30), ['email' => $email, 'token' => $token]).
- Generate a temporary signed route to the confirmation page:
- Feedback: Always show the same success message: "If an account exists, we've sent a link."
- Define Routes in
routes/auth.php:- Add a GET route
/verify-login/{token}namedpasswordless.confirm. - Apply
signedandguestmiddleware.
- Add a GET route
- Implement Confirmation Page:
- Purpose: Prevent email prefetching/scanners from auto-consuming the token.
- Mount Verification:
- Hash the raw token from the URL.
- Verify the token exists and is valid.
- Atomic Consumption:
- On "Continue Login" click, perform an atomic update:
LoginToken::where(...)->whereNull('consumed_at')->update(['consumed_at' => now()]). - If the update fails (returns 0), abort with 403 (token already used or invalid).
- On "Continue Login" click, perform an atomic update:
- Authentication:
- Retrieve User, log in with
Auth::login($user, $remember). - Call
Session::regenerate(). - Mark as verified:
$user->markEmailAsVerified().
- Retrieve User, log in with
- UI Cleanup:
- Remove password fields from
register.blade.php. - Remove password fields from user management (add/edit user).
- Remove "Update Password" section from the profile page.
- Remove password fields from
- Backend Logic Updates:
- Update
Registercomponent andUserControllerto generate a secure random password (Hash::make(Str::password())) when creating users to satisfy DB constraints. - Ensure password fields are ignored during user updates.
- Update
- Authentication Cleanup:
- Remove
CanResetPasswordtrait and interface from theUsermodel. - Remove password reset routes (forgot-password, reset-password).
- Delete the
password_reset_tokenstable via migration. - Remove
verifiedmiddleware from routes (since magic link login verifies the user). - Delete
VerifyEmailControllerand related UI components.
- Remove
- Formatting: Run
vendor/bin/pintto ensure code style compliance. - Testing:
- Verify a user can request a link.
- Verify the link expires after 30 minutes.
- Verify the link cannot be used twice.
- Verify tampering with the URL results in a 403 error.