- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
How Passwords Should Be Stored (and How Breaches Really Happen)
About Post
Assume your users table leaks tomorrow. Not "if". Assume it. Someone has a copy of every row, including the password column, and all the time in the world.
The only question that matters now is: what exactly did they get? A list of passwords, or a list of puzzles that are expensive to solve one by one?
That's what password storage is really about. Not keeping attackers out of the database (that's a different layer), but making sure that when they get in, the passwords are close to worthless.
Five ways to store a password, from worst to right
Let's walk up the ladder. Every rung below the last one still exists in production somewhere.
1. Plain text
The leak is the passwords. Game over, for your app and for every other site where those users reused the same password. If a "forgot password" feature can email you your old password, this is what it's doing.
2. Encrypted
Better-sounding, barely better in practice. Encryption is reversible by design, so the key has to live somewhere your app can reach it. An attacker who gets the database often gets the server too, and the key with it. Passwords never need to be decrypted, so they should never be encrypted.
3. A fast hash (MD5, SHA-1, SHA-256)
Now it's one-way: you store sha256(password) and compare hashes on login. The problem is speed. These algorithms were designed to be fast, and a single gaming GPU can try guesses at a staggering rate. Worse, the same password always produces the same hash, so attackers use precomputed tables, and every user with Password123 falls at once.
4. A fast hash with a salt
A salt is a random value stored next to each hash and mixed into it. Now two users with the same password get different hashes, and precomputed tables are useless. Good. But each guess is still cheap, so weak passwords still fall quickly, just one user at a time.
5. A slow, salted password hash (bcrypt or Argon2id)
This is the answer. Algorithms like bcrypt and Argon2id are deliberately slow and tunable. Checking one password at login takes a fraction of a second, which no user notices. Trying millions of guesses against every row becomes painfully expensive. Argon2id is also memory-hard, which makes it much less friendly to GPUs and custom hardware.
They handle the salt for you, too. The output string contains the algorithm, the cost settings, the salt and the hash all in one:
$hash = password_hash('correct horse battery staple', PASSWORD_ARGON2ID);
// $argon2id$v=19$m=65536,t=4,p=1$c2FsdC4uLg$...
password_verify($input, $hash); // true or false
| Storage | Reversible? | Same password, same value? | Cost per guess |
|---|---|---|---|
| Plain text | Not needed | Yes | None |
| Encrypted | Yes, with the key | Usually | None once the key leaks |
| Fast hash | No | Yes | Tiny |
| Salted fast hash | No | No | Tiny |
| bcrypt / Argon2id | No | No | High, and tunable |
In Laravel, the right thing is the default
If you use Hash::make() (or the hashed cast on the model), you're on rung five. Laravel uses bcrypt by default, and you can switch to Argon2id in config/hashing.php. The cost is controlled by BCRYPT_ROUNDS.
Two details worth knowing:
- Rehash on login. When you raise the cost or change the algorithm, old hashes are still valid. Since Laravel 11, the framework rehashes a user's password automatically when they log in successfully, so your whole table upgrades over time without a migration.
- bcrypt only reads the first 72 bytes. Anything longer is silently ignored. For normal passwords it doesn't matter, but if you allow very long passphrases, Argon2id doesn't have this limit.
How breaches actually turn into stolen accounts
Here's the uncomfortable part. Even with perfect hashing, most account takeovers don't come from cracking your database. They come from:
- Credential stuffing. Attackers take email and password pairs leaked from some other site and try them on yours, automatically. Password reuse makes it work.
- Phishing. The user types their password into a fake login page. No hash can help.
- Weak, common passwords that fall to the first few thousand guesses no matter how they're stored.
So good storage is necessary, but the login form needs defences too.
Defence 1: rate limit the login
Slow down repeated attempts per account and per IP. Laravel's rate limiter makes this short:
$key = Str::lower($request->input('email')).'|'.$request->ip();
if (RateLimiter::tooManyAttempts($key, 5)) {
$seconds = RateLimiter::availableIn($key);
throw ValidationException::withMessages([
'email' => "Too many attempts. Try again in {$seconds} seconds.",
]);
}
if (! Auth::attempt($request->only('email', 'password'))) {
RateLimiter::hit($key, 60);
throw ValidationException::withMessages(['email' => __('auth.failed')]);
}
RateLimiter::clear($key);
This is simplified. Credential stuffing usually comes from many IP addresses, so also watch failures per account and overall, and keep the error message the same whether the email exists or not.
Defence 2: reject passwords that are already leaked
A password that appears in public breach lists is a bad password, however complex it looks. Laravel's password rule can check this for you:
'password' => ['required', 'confirmed', Password::min(12)->uncompromised()],
uncompromised() uses the Have I Been Pwned range API with k-anonymity: only the first five characters of the password's SHA-1 hash leave your server, never the password or the full hash.
Defence 3: don't make the password the only thing
Multi-factor authentication turns a stolen password into a much smaller problem. For admin and staff accounts, it's not optional. For everyone else, offer it and make it easy. Passkeys go further still, because there is no shared secret to phish or reuse.
The rule: hash with bcrypt or Argon2id and never anything you wrote yourself. Then assume some passwords are already known to attackers, and design the login around that: rate limits, breach checks and MFA.
A quick checklist
- Passwords go through
Hash::make(),password_hash()or your framework's equivalent. Nothing else. - No MD5, SHA-anything or encryption for passwords, even "temporarily".
- Cost settings reviewed occasionally, with rehash on login.
- Login rate limited, with the same message for "wrong email" and "wrong password".
- Known-breached passwords rejected at sign-up and on change.
- MFA for anyone with admin powers.
- Passwords never logged, including in request logs and error trackers.
That last one catches more teams than you'd think. When did you last search your logs for the word "password"?

Be first to comment it...