Skip to main content

Command Palette

Search for a command to run...

JWT Authentication in Node.js Explained Simply

Updated
•8 min read•View as Markdown
JWT Authentication in Node.js Explained Simply
S
I'm a passionate software engineer and full-stack MERN developer who loves to turn ideas into scalable, user-centric applications. I have hands-on experience in building modern web solutions using React, Node.js, Express.js, MongoDB, following clean architecture and best development practices. My experience in the Cognizant Healthcare Product Consulting (HPC) program has given me hands-on exposure to SQL, PL/SQL, U.S. healthcare payer systems and TriZetto Facets, and has helped me to further develop my skills in working with enterprise software in domain-driven environments. I enjoy tackling complex technical problems, constantly learning, and building reliable applications that deliver business value.

How does a server know who you are on every request?

Many beginners think the server remembers them after login. That's not true.

HTTP is stateless. Every request arrives like a stranger, with no memory of the last one.

So how does /profile know it is you and not someone else? That is the problem authentication solves.

Authentication means proving who you are before the server lets you in.

Without it, anyone could read your data, edit your account, or delete your orders. Every real app needs it.

After knowing why we need authentication, the next question is: what do we hand the user after login so the server can recognise them again? That answer is a JWT.


What is a JWT?

A JWT (JSON Web Token) is a small, signed piece of text that the server gives you after login.

A JWT is a signed token that carries your identity, so the server can verify you without storing anything.

Analogy: The College Fest Entry Pass

Imagine your college fest. You register once at the desk and get an entry pass.

After that, you never go back to the desk. You just show the pass at every gate.

  • Registration desk = login route

  • Entry pass = JWT

  • Details printed on the pass = payload

  • Official hologram stamp = signature

  • Gate guard = middleware

Fest pass to JWT mapping

This is stateless authentication. The guard does not phone the desk to ask who you are.

He only checks that the stamp is real. The server does the same: it keeps no session list in memory.

Note: Why stateless matters

With sessions, the server stores every logged-in user. With JWT, the token itself carries the proof.

That makes JWT easy to scale across many servers. The next section opens the pass and shows what is printed on it.


What is inside a JWT?

A JWT looks like random text, but it has exactly three parts separated by dots.

xxxxx.yyyyy.zzzzz
header.payload.signature
Structure of a JWT
Part What it holds On the fest pass
Header Token type and signing algorithm Type of pass (student, guest)
Payload Data about the user, like id and role Name and role printed on it
Signature Proof nobody changed the token Official hologram stamp

Here is a decoded example:

// Header
{ "alg": "HS256", "typ": "JWT" }

// Payload
{ "id": 1, "role": "student" }

// Signature
JWT_SECRET = "my-jwt-secret-key"

The signature is made from the header, the payload and a secret key that only your server knows. We skip the maths here. Confidence comes first.

Important Rule: The payload is not secret

The payload is only encoded, not encrypted. Anyone can read it.

Never put passwords or card numbers inside. The signature stops people from changing the token, not from reading it.


How does login work with JWT?

After knowing what a JWT contains, the next question is: who creates it, and when? The server creates it at login.

Analogy: The Registration Desk

At the desk, you show your documents once. If they are correct, the desk prints your pass and stamps it.

Our login route does the same:

  1. The user sends a name and a password.

  2. The server checks them.

  3. If correct, the server signs a JWT with its secret key.

  4. The server sends the token back.

  5. The client keeps the token for later requests.

JWT login flow

Install the packages first:

npm install express jsonwebtoken dotenv

Create a .env file with a long random secret:

JWT_SECRET=put-a-long-random-string-here

Now the login route in server.js:

// Do the Change in package.json file, type="module"

import "dotenv/config";
import express from "express";
import jwt from "jsonwebtoken";

const app = express();

// express.json is middleware used to parse incoming HTTP JSON request to convert into Javascript object.
app.use(express.json());

// Demo user only. Real apps store hashed passwords in a database.
const user = {
    id: 1,
    name: "Sahil",
    role: "student",
    password: "chai123"
};

app.post("/login", (req, res) => {
    const { name, password } = req.body;

    if (name !== user.name || password !== user.password) {
        return res.status(401).json({
            error: {
                message: "Invalid credentials"
            }
        });
    }

    const token = jwt.sign(
        { id: user.id, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: "15m" }
    );

    res.json({ token });
});

expiresIn is important. A pass that never expires is dangerous if someone steals it.


How do we send the token with every request?

Getting the pass is only half the story. You must also show it at every gate.

The standard way is the Authorization header, with the word Bearer before the token:

Authorization: Bearer <your-token>

Try it in Postman. First, log in:

1. Open Postman and click New → HTTP Request.

2. Set the method to POST and the URL to http://localhost:8080/login.

3. Open the Body tab and select raw.

4. Choose JSON from the dropdown on the right.

5. Paste this body:

    json

    {
        "name": "Sahil",
        "password": "chai123"
    }

6. Click Send.

7. You will get Token in this format: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6InN0dWRlbnQifQ.QS45CqzzXwndQUcxI3XdYncvIWjrfwk4c7Z_m84A5I0

The response body contains your token. Copy it, because you will use it in the next section.

After the client sends the token, the next question is: who checks it? That is the job of middleware.


How do we protect routes using tokens?

Analogy: The Gate Guard

The guard stands at the gate and checks every pass before you enter.

  1. Is there a pass at all?

  2. Is the hologram stamp real?

  3. Has the pass expired?

If any answer is no, the guard stops you. Otherwise, you walk in.

Token validation lifecycle

In Express, the guard is a middleware function:

function verifyToken(req, res, next) {
    const header = req.headers.authorization;

    if (!header || !header.startsWith("Bearer ")) {
        return res.status(401).json({
            error: {
                message: "Token missing"
            }
        });
    }

    const token = header.split(" ")[1];

    try {
        const user = jwt.verify(token, process.env.JWT_SECRET);

        req.user = user;
        next();
    } catch (err) {
        res.status(401).json({
            error: {
                message: "Invalid or expired token"
            }
        });
    }
}

app.get("/profile", verifyToken, (req, res) => {
    res.json({ message: `Welcome, user ${req.user.id}`, role: req.user.role });
});

app.listen(8080, () => console.log("Server running on port 8080"));

jwt.verify re-creates the stamp using your secret and compares it. If anything was edited, it throws an error.

Now call the protected route in Postman:

1. Create another request. Set the method to GET and the URL to http://localhost:8080/profile.

2. Open the Authorization tab.

3. Choose Bearer Token from the Type dropdown.

4. Paste your token into the Token field.

5. Click Send.

Postman builds the Authorization: Bearer header for you. You can also see it in the Headers tab.

Now set the Type to No Auth and send again. You get 401 Token missing. That is your route, protected.


How do all these pieces work together?

Now let us join everything in one flow.

Complete JWT flow
Step What happens Fest analogy
1 Client sends credentials to /login Show documents at the desk
2 Server signs and returns a JWT Desk stamps your pass
3 Client sends the token in the header Show the pass at the gate
4 Middleware verifies the token Guard checks the stamp
5 Route runs and sends data You enter the fest

The server never stored a session. The token carried the proof the whole time.


Hands-on assignment: watch a token expire

Change expiresIn to "10s" in the login route. Log in, call /profile straight away, then call it again after 15 seconds. What do you see?

Solution:

The first call returns 200 with your profile. The second returns 401 Invalid or expired token, because jwt.verify rejects any token past its exp time.


Conclusion

  • Authentication proves who you are before the server trusts you.

  • A JWT is a signed token that carries your identity.

  • The header, payload and signature describe, carry and protect the token.

  • Login creates the token, and the client sends it in the Authorization header.

  • Middleware verifies the token and protects your routes.

If this felt like a lot, that is okay. What matters is understanding the flow: login, token, header, verify.


What's Next?

Our demo stored a plain-text password, which no real app should do. In the next article, we fix that with password hashing, then look at roles, OAuth and OpenID Connect. The question: how do we keep an app safe when real users trust it?


If you found this useful, drop a comment or a reaction.

More from this blog

Sahil Gupta | Web Development, Frontend, Backend & DevOps

53 posts

I'm a passionate software engineer and full-stack MERN developer who loves to turn ideas into scalable, user-centric applications. I have hands-on experience in building modern web solutions using React, Node.js, Express.js, MongoDB, following clean architecture and best development practices. My experience in the Cognizant Healthcare Product Consulting (HPC) program has given me hands-on exposure to SQL, PL/SQL, U.S. healthcare payer systems and TriZetto Facets, and has helped me to further dev