- 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
JWT vs Sessions vs API Keys: Which One to Use, and When
About Post
Ask three developers how their API handles authentication and you'll often get three answers that are really the same answer: "We use JWT." For the web app. And the mobile app. And the partner integration. And the cron job on another server.
JWT is a fine tool. But sessions, JWTs and API keys solve different problems, and picking one for everything is how you end up with a logout button that doesn't actually log anyone out, or a partner whose "token" expires every hour and breaks their nightly import.
The quickest way I know to tell them apart is three everyday objects.
Three objects, three ideas
Sessions: the coat check ticket
You hand over your coat and get a numbered ticket. The ticket itself is worthless; it's just a reference. The cloakroom keeps the real thing.
That's a session. After login, the server stores your state (user ID, maybe a few more details) and gives the browser a random session ID in a cookie. On each request, the server looks that ID up. Logging out means throwing away the record, and the ticket instantly means nothing.
JWT: the stamped festival wristband
At a festival, the gate staff don't phone the ticket office for every person. They look at the wristband: right colour, right stamp, today's date. The wristband carries the information and proves itself.
A JWT works the same way. The token contains claims (who you are, when it expires) plus a signature. Any server holding the key can verify it without a database lookup. The catch is the same as with wristbands: once it's on your wrist, it's hard to take back before it expires.
API keys: the access card for the delivery van
The delivery company doesn't log in with a username and password each morning. Its van has an access card that opens one loading bay, issued to the company rather than to a person, valid until someone cancels it.
An API key is a long-lived secret that identifies an application or service, not a person sitting in front of a screen.
Side by side
| Session | JWT | API key | |
|---|---|---|---|
| Identifies | A user in a browser | A user or service, per token | An application or integration |
| State lives | On the server | Inside the token | On the server (key record) |
| Lifetime | Minutes to hours, sliding | Short (minutes), refreshed | Long, until rotated or revoked |
| Instant revoke | Easy: delete the session | Hard without extra state | Easy: mark revoked |
| Main risk | CSRF (cookies are sent automatically) | Theft plus no easy revoke | Leaked in code, logs or repos |
| Natural fit | Your own web app | Distributed services, SSO, OAuth | Server-to-server integrations |
The question that decides it: who is calling?
Most confusion disappears when you ask whether the caller is a person or a program.
A person in a browser, on your own domain? Use sessions with secure, HttpOnly, SameSite cookies. It's boring and battle-tested, logout really works, and JavaScript can't read the cookie if an XSS bug slips through. In Laravel, Sanctum's SPA authentication is exactly this: normal session cookies, plus CSRF protection, for a JavaScript frontend.
A person in a mobile app? Cookies are awkward there, so a token is the usual answer. It doesn't have to be a JWT. Sanctum's personal access tokens are opaque random strings stored hashed in the database, which means you can revoke one from a "log out this device" screen immediately. Store the token in the platform's secure storage (Keychain, Keystore), not in plain app storage.
Many services that need to trust the same identity? This is where JWT shines. An identity provider signs a short-lived token, and each service verifies it locally without calling back. That's why OAuth and OpenID Connect use JWTs so widely. Keep them short-lived and use refresh tokens, because a stolen JWT is valid until it expires.
A program calling your API: a partner's system, an internal cron job, a webhook sender? Use API keys (or OAuth client credentials if you need standards and scopes). Nobody is there to log in, so a token that expires every 15 minutes just creates failures at 3 am.
Rule of thumb: users get sessions or short-lived tokens; services get keys or client credentials. If you're issuing a user's token to a server, or a long-lived key to a browser, stop and rethink.
Doing API keys properly
API keys look like the simplest option, and that's why they're often done carelessly. A few habits make a big difference:
$plain = 'sk_live_' . Str::random(40);
ApiKey::create([
'client_id' => $client->id,
'prefix' => substr($plain, 0, 12), // shown in the UI to identify the key
'key_hash' => hash('sha256', $plain), // never store the key itself
'scopes' => ['invoices:read'],
]);
return $plain; // shown once, then gone
- Store a hash, show the key once. If your database leaks, the keys don't. A fast hash like SHA-256 is fine here (unlike passwords) because the key is long and random, so there's nothing to guess.
- Add a recognisable prefix. It helps people tell keys apart, and secret scanners in tools like GitHub can spot leaked keys with known patterns.
- Scope them. A reporting integration doesn't need write access. Smaller keys mean smaller accidents.
- Support rotation. Allow two active keys per client, so they can switch over without downtime, then revoke the old one.
- Never put them in frontend code. Anything shipped to a browser or app bundle is public. A key in JavaScript is a key for everyone.
The JWT gotchas worth knowing
- A JWT is signed, not encrypted. Anyone can decode the payload. Don't put anything private in it.
- Logout is a lie unless you add state. Deleting the token on the client doesn't stop a stolen copy. Short expiry plus revocable refresh tokens is the usual compromise.
- Pin the algorithm when verifying. Libraries let you specify which algorithms you accept. Do it, and don't let the token's own header decide.
- Don't keep it in
localStoragefor a browser app if you can avoid it. Any XSS can read it. For your own web app, a session cookie is usually the better answer anyway.
The short version
- Your own web app: sessions in secure cookies.
- Mobile app: revocable tokens in secure storage.
- Many services trusting one identity provider: short-lived JWTs.
- Machines and partners: hashed, scoped, rotatable API keys.
Plenty of real systems use all three at once, and that's fine, as long as each one is doing the job it's good at. Which one do you see misused most often in the codebases you've worked on?

Be first to comment it...