- 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 Explained: What's Inside the Token and What Can Go Wrong
About Post
Here's a quick experiment. Take a JWT from any app you're building, paste it into a decoder, and look at the result. No secret key, no password. You can read everything inside it.
The first time developers see this, there's often a small moment of panic. "Wait, isn't it encrypted?" No. And understanding why it doesn't need to be (and when that becomes a problem) is most of what you need to know to use JWTs safely.
Three parts and two dots
A JSON Web Token looks like a random blob, but it's three pieces joined with dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- header
.
eyJzdWIiOiI0MiIsInJvbGUiOiJzdGFmZiIsImV4cCI6MTczOTYxMDAwMH0 <- payload
.
(signature bytes, also base64url) <- signature
(In a real token the three parts sit on one line: header.payload.signature.)
Decode the first two parts and you get plain JSON:
// header
{ "alg": "HS256", "typ": "JWT" }
// payload (the "claims")
{ "sub": "42", "role": "staff", "exp": 1739610000 }
- Header: which algorithm was used to sign the token.
- Payload: the claims. Who the user is (
sub), when the token expires (exp, a Unix timestamp), and whatever else you put in. - Signature: a cryptographic signature of the header and payload, made with a key only the server knows.
Base64 is not encryption
The header and payload are only base64url-encoded. Encoding is like writing a message in a different alphabet: anyone who knows the alphabet can read it. There's no key involved.
What the signature gives you is integrity, not secrecy. Think of a sealed glass box: everyone can see what's inside, but nobody can change it without breaking the seal. If a user edits the payload to say "role": "admin", the signature no longer matches and the server rejects the token.
So the rule is simple: never put secrets in a JWT payload. No passwords, no personal data you wouldn't show the user, no internal details. A user ID and a role are fine. A national ID number is not.
(There is an encrypted variant, JWE, but when people say "JWT" they almost always mean the signed kind, JWS.)
How the server checks a token
On each request the server:
- Splits the token into its three parts.
- Recomputes the signature over the header and payload with its own key.
- Compares it with the signature in the token.
- Checks the claims: is
expin the future? Is the issuer and audience what we expect?
No database lookup needed. That's the big selling point of JWTs: any server holding the key can verify a token on its own, which is handy across services. It's also the root of their biggest weakness.
The revocation problem
A user logs out. Or you ban them. Or their phone is stolen. With a session stored on the server, you delete the session and they're out instantly.
With a JWT, the token is valid until it expires, because the whole point was that the server doesn't check anything stored. You've handed out a signed permission slip and you can't take it back.
The common ways teams deal with it:
- Short-lived access tokens (minutes, not days) plus a refresh token that is stored on the server and can be revoked.
- Refresh token rotation: every refresh issues a new refresh token and invalidates the old one, so a stolen one is quickly useless.
- A denylist of revoked token IDs (the
jticlaim) checked on each request. It works, but notice you've just reintroduced a lookup on every request, which is what JWTs were meant to avoid.
Which leads to an honest question worth asking: for a single web app talking to its own backend, do you need JWTs at all? A plain server-side session or an opaque token stored in the database (which is what Laravel Sanctum's API tokens are) is often simpler and revocable by design.
The "alg: none" story
The JWT standard includes an algorithm called none, meaning "this token isn't signed". Around 2015, security researchers showed that several popular JWT libraries would trust the alg value from the token's own header. An attacker could take a token, change the payload, set "alg": "none", remove the signature, and some servers would accept it.
A related trick was algorithm confusion: telling a server that expected an RSA signature to verify with HMAC instead, using the public key as the HMAC secret.
Libraries have long since been fixed, but the lesson still matters: the server decides the algorithm, never the token. Always configure your library with the exact algorithm you expect.
Verification checklist: pin the algorithm, verify the signature, check exp, check issuer and audience, and keep the signing key out of your repository. Decoding a token is not the same as verifying it.
Where to store it in the browser
This one starts arguments, so here's the trade-off plainly:
localStorage: easy, but any JavaScript on the page can read it. One XSS bug and the token is gone.httpOnlycookie: JavaScript can't read it, which protects it from XSS theft. But the browser sends it automatically, so you need CSRF protection and sensibleSameSitesettings.
For web apps I lean towards httpOnly, Secure, SameSite cookies. In a mobile app, the platform's secure storage (Keychain on iOS, Keystore on Android, or expo-secure-store in Expo) is the right home.
If you remember one thing
A JWT is a signed note, not a locked box. Anyone can read it, nobody can forge it, and once it's out you can't easily take it back. Keep the payload boring, keep the lifetime short, and let the server, not the token, decide how it's verified.
Do you use JWTs for your own web apps, or have you gone back to sessions? I'd like to hear what tipped the decision.

Be first to comment it...