Ever logged into an app, closed the tab, come back, and you're still logged in — no password needed? That's almost always JWT doing its job behind the scenes.
JWT (JSON Web Token) is one of the most common ways to handle authentication in modern backends. But a lot of developers use it without really understanding what's happening — and that's exactly where security bugs sneak in.
Let's fix that. By the end of this post you'll know what a JWT actually is, how to use it in a Node.js + Express app, and the mistakes that quietly break real apps.
What is a JWT, really?
A JWT is just a string with three parts, separated by dots:
xxxxx.yyyyy.zzzzz
│ │ │
header payload signature
-
Header — says which algorithm signed the token (e.g.
HS256). -
Payload — the actual data (like
userId,role, and an expiry time). This is not encrypted — it's just Base64-encoded. Anyone can read it. - Signature — a cryptographic stamp created using a secret only your server knows. This is what stops people from faking tokens.
Want to see this for yourself? Paste any token into a free JWT decoder and you'll instantly see the header and payload. Notice you can read everything without the secret — that's the key lesson: never put passwords or sensitive data in a JWT payload.
Creating a token (login)
Install the library:
npm install jsonwebtoken
When a user logs in successfully, sign a token:
import jwt from 'jsonwebtoken'
// On successful login:
const token = jwt.sign(
{ userId: user._id, role: user.role }, // payload
process.env.JWT_SECRET, // secret (keep it in .env!)
{ expiresIn: '7d' } // auto-expiry
)
res.json({ token })
Three things to notice:
- Keep the payload small — just an id and role, not the whole user object.
- The secret lives in an environment variable, never hardcoded.
- Always set
expiresIn. A token that never expires is a token that can be stolen forever.
Verifying a token (protecting routes)
Now create a middleware that checks the token on every protected request:
export function auth(req, res, next) {
const header = req.headers.authorization || ''
const token = header.startsWith('Bearer ') ? header.slice(7) : null
if (!token) return res.status(401).json({ message: 'No token provided' })
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded // { userId, role, iat, exp }
next()
} catch (err) {
return res.status(401).json({ message: 'Invalid or expired token' })
}
}
Use it on any route you want to protect:
app.get('/api/profile', auth, (req, res) => {
res.json({ userId: req.user.userId })
})
That's the whole core loop: sign on login, verify on every request.
The mistakes that break real apps
These are the ones I see again and again:
-
Trusting the payload without verifying. Decoding a token (reading it) is not the same as verifying it. Always use
jwt.verify(), never just decode. -
A weak or leaked secret. If your secret is
secret123or committed to GitHub, anyone can forge valid tokens. Use a long random string. -
No expiry. Always set
expiresIn. Pair short-lived access tokens with a longer refresh token for a better experience. -
Storing the token in
localStoragecarelessly. It's vulnerable to XSS. For sensitive apps, anhttpOnlycookie is safer. - Putting secrets in the payload. Remember — the payload is readable by anyone. No passwords, no card numbers.
Debugging tip
When something "just doesn't work," the fastest fix is to actually look at the token. Drop it into a JWT decoder and check:
- Is the
exp(expiry) in the past? → token expired. - Is the payload what you expect? → maybe you signed the wrong data.
- Wrong
algin the header? → algorithm mismatch.
90% of JWT bugs become obvious the moment you see the decoded token.
Wrapping up
JWT isn't magic — it's a signed string that says "this user is who they claim to be, and here's proof my server made it." Get the basics right (small payload, strong secret, always expire, always verify) and you've covered most of what breaks in production.
I teach hands-on backend development (Node.js, Express, MongoDB, Redis) at AS Backend Institute. If you're learning backend and want more practical guides like this, come say hi. 🚀
Top comments (0)