JWT Authentication in Node.js: A Practical Guide (with Express)
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