
September 07, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
Stored passwords age badly. A Laravel authentication stack that still writes bcrypt hashes with low cost factors—or that never upgrades hashes after you change config/hashing.php—leaves user credentials weaker than modern guidance recommends. Laravel Password Rehashing and Argon2id Setup solves both problems: Argon2id becomes your default algorithm, and Laravel rehashes each password transparently the next time the user logs in with the correct plaintext. You do not need a disruptive reset campaign for most applications. This guide covers configuration on Laravel 12 and 13 with PHP 8.3 or higher, the exact rehash trigger points, production tuning, and the mistakes I have seen on client portals and eCommerce apps.
HASH_DRIVER=argon2id in .env, tune memory and time in config/hashing.php, and Laravel password rehashing and Argon2id setup completes automatically when users log in—Hash::needsRehash() detects outdated bcrypt hashes and replaces them with Argon2id.What is Laravel password rehashing and why should you use Argon2id?
Password hashing is one-way: you never store plaintext, and you never decrypt. When a user registers or changes a password, Laravel calls Hash::make(), which delegates to PHP's password_hash() using the driver defined in config/hashing.php. Verification uses Hash::check($plain, $hash).
Rehashing means: the stored hash is still valid, but its parameters—or the algorithm itself—are outdated relative to your current config. Laravel detects that with Hash::needsRehash($storedHash) and, on a successful login, writes a fresh hash without asking the user to pick a new password. That is the core of a zero-downtime migration from bcrypt to Argon2id.
Argon2id won the Password Hashing Competition and is the recommended choice in the OWASP Password Storage Cheat Sheet. It combines side-channel resistance from Argon2i with GPU-hardness from Argon2d. Bcrypt remains acceptable for legacy systems, but for greenfield Laravel 13 apps on PHP 8.5—or existing Laravel 12 deployments on PHP 8.3—Argon2id is the sensible default in 2026.
On legal-tech portals and client dashboards—systems like those described in our Mijar Law Associates portfolio case—users log in infrequently but expect document access to remain secure for years. Rehashing at login closes the gap between "we changed config last month" and "every stored hash still reflects 2019 bcrypt rounds."
Bcrypt vs Argon2id in Laravel
| Criterion | bcrypt | argon2id |
|---|---|---|
| PHP constant | PASSWORD_BCRYPT | PASSWORD_ARGON2ID |
| GPU resistance | Moderate | Strong (memory-hard) |
| Tunable axes | Cost factor (rounds) | Memory, time, threads |
| Laravel driver key | bcrypt | argon2id |
| Typical migration | Legacy default | Target for 2026 apps |
| Rehash trigger | Same needsRehash() API | Same needsRehash() API |
Both drivers produce strings that Hash::check() verifies correctly regardless of which driver is currently configured. That backward compatibility is what makes lazy migration practical.
How do you configure Argon2id as the default Laravel hash driver?
Start with server prerequisites. Argon2id requires PHP compiled with Argon2 support—verify before changing production config:
php -r "var_dump defined('PASSWORD_ARGON2ID');"
php -m | grep -i sodium You should see bool(true). On Ubuntu 24.04 with PHP 8.5, the standard php8.5-fpm package includes this. If the constant is missing, install the appropriate PHP build or enable sodium before proceeding. Our Ubuntu server setup guide covers multi-version PHP installs on production boxes.
Step 1: Environment and config
In Laravel 12 and 13, edit .env:
HASH_DRIVER=argon2id Then confirm config/hashing.php (structure is consistent across recent releases—consult the Laravel hashing documentation for your exact version):
return [
'driver' => env('HASH_DRIVER', 'bcrypt'),
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12),
'verify' => true,
],
'argon' => [
'memory' => 65536,
'threads' => 1,
'time' => 4,
'verify' => true,
],
'argon2id' => [
'memory' => env('ARGON_MEMORY', 65536),
'threads' => env('ARGON_THREADS', 1),
'time' => env('ARGON_TIME', 4),
'verify' => true,
],
'rehash_on_login' => true,
]; Clear config cache after deploy:
php artisan config:clear
php artisan config:cache New registrations and password resets immediately receive Argon2id hashes. Existing bcrypt users stay on bcrypt until they authenticate—exactly the behaviour you want.
Step 2: Verify in Tinker
php artisan tinker
>>> $hash = Hash::make('test-password-123');
>>> str_starts_with($hash, '$argon2id$');
=> true
>>> Hash::check('test-password-123', $hash);
=> true If the prefix shows $2y$, your config cache is stale or HASH_DRIVER is not loaded in that environment.
When does Laravel automatically rehash passwords during authentication?
Automatic rehashing is not magic middleware you must install—it lives in the authentication guard. When a user logs in through the default session guard, Laravel validates credentials, then checks whether the stored hash matches current policy.
The flow inside Illuminate\Auth\SessionGuard (paraphrased for clarity):
- Retrieve user by credentials.
- Verify plaintext with
Hash::check()against$user->password. - If valid and
Hash::needsRehash($user->password)returns true, hash the plaintext again with the current driver and persist it. - Establish the authenticated session.
This runs for standard Auth::attempt() calls and Fortify/Breeze login routes that use the session guard. It does not run for API token authentication via Sanctum—token auth never sees the password after initial issuance. If your REST API shares the same user table, mobile clients keep working; only password-based flows trigger rehashing.
Custom login controllers
If you bypass Auth::attempt() and manually validate—a pattern I still see in older codebases—you must rehash yourself:
use Illuminate\Support\Facades\Hash;
if (! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
if (Hash::needsRehash($user->password)) {
$user->forceFill([
'password' => Hash::make($request->password),
])->save();
}
Auth::login($user, $request->boolean('remember')); Never rehash when Hash::check() fails. Doing so would leak whether a user exists and create corrupt hashes.
Password reset and change flows
Password reset always writes a fresh hash via Hash::make() with the current driver—no needsRehash() call required. The same applies to profile "change password" forms. In my experience working on production Laravel applications, reset-heavy months (after a security announcement) accelerate Argon2id adoption because those users never need a login-time upgrade.
How do you migrate existing bcrypt users to Argon2id without forcing a reset?
The lazy migration strategy is the production default for good reason. You change config, deploy, and let logins do the work. For a Laravel eCommerce platform with tens of thousands of customer accounts, a forced reset would spike support tickets and cart abandonment. Rehash-on-login avoids that.
Monitoring migration progress
Run a read-only audit query against MySQL 9.7 or PostgreSQL 18—prefix patterns tell you algorithm distribution:
SELECT
CASE
WHEN password LIKE '$argon2id$%' THEN 'argon2id'
WHEN password LIKE '$2y$%' THEN 'bcrypt'
WHEN password LIKE '$2a$%' THEN 'bcrypt'
ELSE 'other'
END AS algo,
COUNT(*) AS total
FROM users
GROUP BY algo; Schedule this weekly via a read-only scheduled Artisan command until bcrypt counts plateau near zero. Log results; do not expose them publicly.
Dormant accounts
Users who have not logged in since your deploy keep bcrypt hashes indefinitely. That is acceptable for most businesses—the hash is still one-way. Options for stricter policies:
- Force password reset on next login via a
password_changed_atflag (custom middleware). - Expire dormant accounts per your retention policy.
- After 12–18 months, email inactive users a voluntary reset link—not a blind invalidation.
On a legal-tech portal I built, infrequent client logins made lazy migration the only realistic path; partners would not tolerate a blanket reset before a court deadline.
Bulk rehash is impossible (by design)
You cannot convert bcrypt to Argon2id offline without the plaintext password. Any tool claiming otherwise is misunderstanding password hashing. The PHP password_hash() documentation is explicit: verification needs the original input. Plan for login-time upgrades, not batch SQL updates.
How do you tune Argon2id memory and time cost for production Laravel apps?
Argon2id parameters trade security against login latency and RAM. OWASP suggests targeting roughly 600ms hash time on your production hardware. Measure—not guess.
Benchmark on the server
php artisan tinker
>>> $start = hrtime(true);
>>> Hash::make('benchmark-password');
>>> (hrtime(true) - $start) / 1e6;
=> 487.3 Run this on the same PHP-FPM workers that serve auth—not your laptop. Shared hosting with tight memory limits may reject high memory values; start at 65536 KiB (64 MB) and increase if latency stays under your budget.
| Parameter | Env key | Conservative start | Higher security |
|---|---|---|---|
| Memory (KiB) | ARGON_MEMORY | 65536 | 131072–262144 |
| Time (iterations) | ARGON_TIME | 4 | 5–6 |
| Threads | ARGON_THREADS | 1 | 2 (match CPU cores cautiously) |
Raising parameters marks existing Argon2id hashes for rehash on next login—the same needsRehash() mechanism. That is useful when you harden policy annually; plan deploys outside peak login windows.
PHP-FPM and worker memory
Each concurrent login allocates Argon2 memory inside the worker. On a 2 GB VPS running PHP 8.4 with 10 FPM children, aggressive ARGON_MEMORY=262144 can cause OOM under burst login traffic. Monitor with pm.max_children headroom. For high-traffic apps, consult our testing and optimization service or load-test auth endpoints before promoting config changes.
Security adjacent hardening
Strong hashing is one layer. Pair Argon2id with:
- Two-factor authentication for admin and client portals.
- Rate limiting on login routes (
RateLimiteror Fortify throttling). - Dependency vulnerability scanning in CI so auth packages stay patched.
- A proper password generator in internal docs so staff stop reusing credentials on staging.
For PostgreSQL-backed Laravel apps, ensure the password column remains at least varchar(255)—Argon2id strings can exceed bcrypt length at high memory settings.
What production mistakes break Laravel password rehashing?
These show up repeatedly during audits and post-deploy support calls:
- Cached config with old driver. Deploy sets
HASH_DRIVER=argon2idbutconfig:cachewas built from stale CI artefacts. Symptom: new hashes still show$2y$. - Custom auth without rehash logic. Replacing Fortify/Breeze login but forgetting the
needsRehashblock leaves bcrypt forever. - Mass-assignment guards. If
passwordis not fillable and you useupdate()instead offorceFill(), rehash silently fails. Check logs. - Read replicas and replication lag. Rehash writes to primary; immediate re-auth against a lagging replica sees old hash—rare but confusing. See read replica setup for session stickiness patterns.
- Importing users from WordPress 7.1. WordPress may use phpass or bcrypt variants. Laravel verifies many formats, but plan a reset or login-time upgrade—do not assume Argon2id config rewrites imported hashes automatically without login.
After changing hashing policy on sister sites that share Deployer 7 pipelines, I always verify one staging login end-to-end before swapping the production symlink. The extra five minutes beats explaining to a law firm why partners cannot access uploaded affidavits.
If you maintain legacy Laravel 11 apps, note that 11 reached EOL in March 2026—upgrade to Laravel 12 (PHP 8.2+) or 13 (PHP 8.3+) before investing in hashing hardening. Framework security support matters as much as algorithm choice. Read why Laravel fits Nepali business constraints for upgrade prioritisation context.
Key Takeaways
- Set
HASH_DRIVER=argon2id, tuneARGON_MEMORY/ARGON_TIME, and clear config cache so new hashes use Argon2id immediately. - Laravel rehashes outdated passwords automatically on successful session login via
Hash::needsRehash()—custom auth must replicate that logic. - Lazy migration from bcrypt requires no plaintext access; monitor prefix counts in SQL until bcrypt rows approach zero.
- Benchmark hash duration on production PHP-FPM workers and target roughly 300–800ms—not desktop Tinker results.
- Combine Argon2id with 2FA, rate limiting, and regular framework upgrades for defence in depth.
- Never rehash failed login attempts; never batch-convert hashes without user passwords.
People Also Ask
Does Laravel 13 support Argon2id out of the box?
Yes. Laravel 13 on PHP 8.3 or higher includes an argon2id driver in config/hashing.php. Set HASH_DRIVER=argon2id and confirm PASSWORD_ARGON2ID exists in your PHP build. No Composer packages are required beyond the framework.
Will changing to Argon2id log out existing users?
No. Existing sessions remain valid. Only the stored hash changes after the next successful password login. Users keep browsing; the upgrade happens silently in the background during authentication.
Can Sanctum or Passport API tokens trigger password rehashing?
No. Token-based API auth does not present the user password on each request, so needsRehash() never runs. Users who only use mobile tokens stay on bcrypt until they log in through a password form or reset their password.
Is bcrypt still safe if I cannot enable Argon2id on shared hosting?
Bcrypt with cost 12 or higher remains acceptable for many applications. Increase BCRYPT_ROUNDS and enable rehash-on-login so raising rounds upgrades users over time. Move to Argon2id when your host supports it or when you migrate to a VPS—our Linux system administration service handles that transition regularly.
Ship stronger credentials without disrupting users
Laravel Password Rehashing and Argon2id Setup is a low-risk, high-value hardening step: one config change, automatic upgrades at login, and measurable migration via simple SQL. Pair it with 2FA on sensitive portals—like those we document for Notary Nepal and Court Marriage In Nepal—and you close the most common gap between "we take security seriously" and what is actually stored in users.password.
Need Argon2id configured, auth audited, or a Laravel 12/13 upgrade planned before hashing changes go live? Review our Laravel web development services or ongoing support packages, browse the full portfolio, and contact us with your stack details—PHP version, user count, and whether you use Fortify, Breeze, or custom auth.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

