Diagnosing Two‑Factor Authentication Issues in Laravel Jetstream
When Jetstream’s 2FA throws “The code is invalid” or never delivers codes, the root cause often lies in mail, session, or database configuration. This guide walks you through symptoms, causes, ordered checks, fixes, and when to ask for help.
28 Oct 2025, 06:52 UTC

Problem Statement
After enabling Laravel Jetstream’s two‑factor authentication (2FA), users report one of the following:
- Login fails with “The code is invalid.”
- 2FA code is sent but never arrives.
- Session persists after 2FA but the user is logged out on the next request.
- 2FA triggers on every request even after verification.
- 2FA works on some browsers/devices but not others.
- Code displays incorrectly in the UI (e.g., missing digits).
These symptoms usually stem from misconfiguration rather than a bug in Jetstream. The goal of this guide is to help you identify the root cause quickly, apply the appropriate fix, and verify the result.
Root Causes & Quick Diagnostic Table
| Symptom | Likely Root Cause |
|---|---|
| Invalid code on login | Wrong or missing two_factor_secret column, encryption mismatch, or time drift. |
| Code never arrives | Mail driver misconfigured or blocked. |
| Session lost after 2FA | Session driver set to array or cookie domain mismatch. |
| 2FA on every request | Missing verified flag in session due to session loss. |
| Device‑specific failure | Clock skew on the device or browser cache interfering with TOTP. |
| Code UI display issue | Front‑end formatting or CSS truncation. |
Ordered Checks
- Verify Jetstream Feature Flag
Run
php artisan jetstream:install livewireto regenerate the stack and confirmconfig/jetstream.phpcontains:return [ 'features' => [ // ... other features 'two_factor_authentication', ], ];Missing the flag means Jetstream will not add the necessary routes or UI.
- Check Database Schema
Ensure the
userstable hastwo_factor_secretandtwo_factor_recovery_codescolumns and that they are encrypted. Run:php artisan migrateInspect the migration file: it should use
$table->text('two_factor_secret')->nullable();and$table->text('two_factor_recovery_codes')->nullable();. If you altered the column type (e.g., tostring), revert totextand re‑run migrations. - Validate Mail Configuration
Open
.envand confirm:MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=your_user MAIL_PASSWORD=your_pass MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=[contact removed] MAIL_FROM_NAME="Example"Send a test email:
php artisan tinker→Mail::raw('Test', fn($m){$m->to('[contact removed]')->subject('Test');});. If the email fails, correct the driver settings or contact your mail provider. - Inspect Session Driver
Open
config/session.phpand ensuredriveris not set toarray(which discards session data). Common safe drivers:file,cookie,database,redis. Verify the session cookie domain matches the app’s URL. - Check Browser Time & Clock Sync
2FA uses TOTP, which relies on the client’s clock. Open the 2FA QR code in a TOTP app and confirm the device’s time is accurate. On browsers, check the system time or use
Date.now()in the console to ensure it matches the server time. - Review Front‑end Rendering
Inspect the 2FA input field in the browser’s dev tools. Ensure the
<input>hasmaxlength=6and the CSS does not truncate digits. If you customized the Livewire component, revert to the default to rule out UI bugs.
Fixes Tied to Findings
- Missing
two_factor_secretcolumn or encryption mismatch – Re‑run the migration after restoring the original column definition. Do not modify the column type without backing up data. - Mail driver misconfigured – Update
.envwith correct credentials, then runphp artisan config:cacheand test again. - Session driver set to
array– Change tofileorredis, clear existing sessions withphp artisan session:tableif using database, thenphp artisan migrateandphp artisan config:clear. - Clock skew – Sync the client device or use an NTP service. For browsers, advise users to enable automatic time sync.
- UI truncation – Remove any CSS
overflow:hiddenortext-overflow: ellipsison the input, and settype="text" maxlength="6".
Verification Checklist
- Enable debug mode:
APP_DEBUG=truein.env. Attempt 2FA and inspectstorage/logs/laravel.logfor exceptions. - After login, open the browser’s developer console and confirm a cookie named
laravel_sessionexists and persists across requests. - Generate a new 2FA secret via the UI, copy the QR code, scan with a TOTP app, and verify the code matches the one displayed in the app.
- Send a test email manually as shown earlier and confirm receipt.
Escalation Criteria
If all the above checks pass and the problem persists, consider:
- Reviewing custom middleware that might interfere with the
EnsureTwoFactorIsVerifiedguard. - Checking for conflicting session cookies from subdomains or third‑party services.
- Consulting the Laravel community or opening an issue on the Jetstream GitHub repository with detailed logs (excluding sensitive data).
Remember: 2FA is a security feature; any changes to encryption or session handling should be audited and tested in a staging environment before production deployment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.