Introduction
JSON Web Token (JWT) authentication is a common way to secure Node.js APIs. A server creates a signed token after successfully verifying a user’s credentials. The client can then send that token when requesting protected resources. In this chapter, you will practice JWT authentication step by step, starting with token creation and verification and progressing to login, authentication middleware, protected routes, MongoDB, and a complete JWT authentication API. Node.js JWT Authentication practice questions with Solutions help to build concepts.
Question 1: How do you create a JWT in Node.js?
Problem
Create a simple Node.js program that generates a JSON Web Token containing a username.
Solution
First, create a Node.js project:
npm init -y
Install the JWT package:
npm install jsonwebtoken
Create index.js:
const jwt = require("jsonwebtoken");
const secretKey = "my-secret-key";
const user = {
username: "rahul"
};
const token = jwt.sign(
user,
secretKey
);
console.log("JWT Token:");
console.log(token);
Output
You will get a token similar to:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The exact token will be different.
Step-by-Step Explanation
Import the JWT package:
const jwt = require("jsonwebtoken");
Create a secret key:
const secretKey = "my-secret-key";
Create the user information:
const user = {
username: "rahul"
};
Generate the token:
const token = jwt.sign(
user,
secretKey
);
The first argument is the payload.
The second argument is the secret used to sign the token.
Important Point
In a real application, do not hard-code the JWT secret in your source code. Store it in an environment variable.
Question 2: How do you create a JWT with an expiration time?
Problem
Create a JWT that expires after one hour.
Solution
const jwt = require("jsonwebtoken");
const secretKey = "my-secret-key";
const user = {
userId: 101,
username: "rahul"
};
const token = jwt.sign(
user,
secretKey,
{
expiresIn: "1h"
}
);
console.log(token);
Step-by-Step Explanation
The third argument of jwt.sign() contains options:
{
expiresIn: "1h"
}
This tells JWT that the token should expire after one hour.
You can also use values such as:
{
expiresIn: "15m"
}
or:
{
expiresIn: "7d"
}
Example
const token = jwt.sign(
{
username: "rahul"
},
secretKey,
{
expiresIn: "1h"
}
);
Important Point
Token expiration limits how long a token can be accepted. It does not replace other security controls such as secure storage, HTTPS, and appropriate authorization.
Question 3: How do you verify a JWT in Node.js?
Problem
Create a program that generates a JWT and then verifies it.
Solution
const jwt = require("jsonwebtoken");
const secretKey = "my-secret-key";
const user = {
username: "rahul"
};
const token = jwt.sign(
user,
secretKey,
{
expiresIn: "1h"
}
);
console.log("Token created.");
try {
const decoded =
jwt.verify(
token,
secretKey
);
console.log(
"Token is valid."
);
console.log(
decoded
);
} catch (error) {
console.log(
"Token is invalid."
);
}
Output
Token created.
Token is valid.
{ username: 'rahul', iat: ..., exp: ... }
Step-by-Step Explanation
Create the token:
const token = jwt.sign(
user,
secretKey
);
Then verify it:
jwt.verify(
token,
secretKey
);
If the token is valid, the decoded payload is returned.
If the token is invalid or expired, an error is thrown.
Important Point
The same secret/key configuration used to sign the token must be available for verification.
Question 4: How do you create a basic JWT login API using Express.js?
Problem
Create an Express.js login API that checks a sample username and password and returns a JWT after successful login.
Solution
Install Express and JWT:
npm install express jsonwebtoken
Create index.js:
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const secretKey = "my-secret-key";
const user = {
username: "rahul",
password: "12345"
};
app.post("/login", (req, res) => {
const {
username,
password
} = req.body;
if (
username !== user.username ||
password !== user.password
) {
return res.status(401).json({
success: false,
message:
"Invalid username or password."
});
}
const token = jwt.sign(
{
username: user.username
},
secretKey,
{
expiresIn: "1h"
}
);
res.json({
success: true,
message:
"Login successful.",
token: token
});
});
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Test the API
Send:
POST http://localhost:3000/login
JSON body:
{
"username": "rahul",
"password": "12345"
}
Response
{
"success": true,
"message": "Login successful.",
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Step-by-Step Explanation
The client sends credentials:
req.body
The server checks them:
if (
username !== user.username ||
password !== user.password
)
If the credentials are correct, the server creates a JWT:
const token = jwt.sign(
{
username: user.username
},
secretKey,
{
expiresIn: "1h"
}
);
The token is then returned to the client.
Important Point
This example uses plain-text credentials only to demonstrate the JWT flow. Real applications should use password hashing such as bcrypt and store users in a database.
Question 5: How do you read a JWT from the Authorization header?
Problem
Read a Bearer token sent by a client in an Express.js request.
Solution
const express = require("express");
const app = express();
app.get("/profile", (req, res) => {
const authHeader =
req.headers.authorization;
console.log(
"Authorization Header:",
authHeader
);
res.json({
message:
"Authorization header received."
});
});
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Send the Request
The client sends:
Authorization: Bearer YOUR_TOKEN
For example:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Extract Only the Token
You can use:
const authHeader =
req.headers.authorization;
const token =
authHeader &&
authHeader.split(" ")[1];
console.log(token);
Step-by-Step Explanation
The complete header looks like:
Bearer eyJhbGciOiJIUzI1NiIs...
The split() operation separates it into:
Bearer
and:
eyJhbGciOiJIUzI1NiIs...
The second part is the JWT.
Important Point
A missing token should not be treated as an authenticated request.
Question 6: How do you create JWT authentication middleware?
Problem
Create Express middleware that extracts and verifies a JWT before allowing a request to continue.
Solution
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const secretKey = "my-secret-key";
function authenticateToken(
req,
res,
next
) {
const authHeader =
req.headers.authorization;
const token =
authHeader &&
authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({
success: false,
message:
"Access token required."
});
}
try {
const user =
jwt.verify(
token,
secretKey
);
req.user =
user;
next();
} catch (error) {
return res.status(403).json({
success: false,
message:
"Invalid or expired token."
});
}
}
app.get(
"/profile",
authenticateToken,
(req, res) => {
res.json({
success: true,
message:
"Welcome to your profile.",
user:
req.user
});
}
);
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Step-by-Step Explanation
First, read the header:
const authHeader =
req.headers.authorization;
Extract the token:
const token =
authHeader &&
authHeader.split(" ")[1];
Check whether a token exists:
if (!token) {
...
}
Verify the token:
const user =
jwt.verify(
token,
secretKey
);
Store the decoded information:
req.user = user;
Then allow the request to continue:
next();
Protected Route
The middleware is attached here:
app.get(
"/profile",
authenticateToken,
(req, res) => {
...
}
);
The request must pass through authenticateToken before reaching the profile handler.
Important Point
Authentication middleware is useful because you can reuse the same verification logic across multiple protected routes.
Question 7: How do you create login and protected routes using JWT?
Problem
Create an Express.js application with:
- Login route
- JWT generation
- Authentication middleware
- Protected profile route
Solution
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const secretKey = "my-secret-key";
const user = {
id: 101,
username: "rahul",
password: "12345"
};
// Login
app.post("/login", (req, res) => {
const {
username,
password
} = req.body;
if (
username !== user.username ||
password !== user.password
) {
return res.status(401).json({
message:
"Invalid username or password."
});
}
const token =
jwt.sign(
{
id: user.id,
username: user.username
},
secretKey,
{
expiresIn: "1h"
}
);
res.json({
message:
"Login successful.",
token: token
});
});
// Authentication middleware
function authenticateToken(
req,
res,
next
) {
const authHeader =
req.headers.authorization;
const token =
authHeader &&
authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({
message:
"Token required."
});
}
try {
const decoded =
jwt.verify(
token,
secretKey
);
req.user =
decoded;
next();
} catch (error) {
return res.status(403).json({
message:
"Invalid or expired token."
});
}
}
// Protected route
app.get(
"/profile",
authenticateToken,
(req, res) => {
res.json({
message:
"Protected profile data.",
user:
req.user
});
}
);
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Step-by-Step Flow
POST /login
↓
Check Credentials
↓
Create JWT
↓
Return Token
↓
Client Sends Token
↓
GET /profile
↓
Authentication Middleware
↓
Verify JWT
↓
Protected Route
Test Login
POST /login
Body:
{
"username": "rahul",
"password": "12345"
}
Copy the token from the response.
Test Protected Route
GET /profile
Header:
Authorization: Bearer YOUR_TOKEN
Example Response
{
"message": "Protected profile data.",
"user": {
"id": 101,
"username": "rahul",
"iat": 1234567890,
"exp": 1234571490
}
}
Important Point
The iat and exp fields are JWT timestamps. They are automatically added when appropriate JWT options are used.
Question 8: How do you use JWT with bcrypt password authentication?
Problem
Create a login API that checks a password using bcrypt and generates a JWT only when the password is correct.
Solution
Install the packages:
npm install express bcrypt jsonwebtoken
Create index.js:
const express = require("express");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const secretKey = "my-secret-key";
const user = {
username: "rahul",
password:
"$2b$10$example-hash"
};
// Login
app.post("/login", async (req, res) => {
try {
const {
username,
password
} = req.body;
if (
username !== user.username
) {
return res.status(401).json({
message:
"Invalid username or password."
});
}
const passwordCorrect =
await bcrypt.compare(
password,
user.password
);
if (!passwordCorrect) {
return res.status(401).json({
message:
"Invalid username or password."
});
}
const token =
jwt.sign(
{
username:
user.username
},
secretKey,
{
expiresIn: "1h"
}
);
res.json({
message:
"Login successful.",
token:
token
});
} catch (error) {
res.status(500).json({
message:
"Login failed."
});
}
});
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
Important Correction for Practice
The following is only a placeholder:
"$2b$10$example-hash"
It is not a valid bcrypt hash for a real password.
For a real example, first create a hash:
const bcrypt = require("bcrypt");
async function createHash() {
const hash =
await bcrypt.hash(
"12345",
10
);
console.log(hash);
}
createHash();
Then use the generated hash in your database.
Step-by-Step Authentication Flow
User enters password
↓
Find user
↓
bcrypt.compare()
↓
Password correct?
↙ ↘
YES NO
↓ ↓
Create JWT Reject
↓
Return JWT
Important Point
JWT does not replace password hashing. JWT and bcrypt solve different problems:
bcrypt → Protect stored passwords
JWT → Represent an authenticated session/request
Question 9: How do you use JWT authentication with MongoDB?
Problem
Create a Node.js application that:
- Finds a user in MongoDB
- Checks the password using bcrypt
- Creates a JWT
- Protects a profile route
Solution
Install the packages:
npm install express mongodb bcrypt jsonwebtoken
Create index.js:
const express = require("express");
const {
MongoClient
} = require("mongodb");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const uri =
"mongodb://127.0.0.1:27017";
const client =
new MongoClient(uri);
const JWT_SECRET =
process.env.JWT_SECRET ||
"development-secret-change-this";
let users;
// Connect to MongoDB
async function connectDatabase() {
await client.connect();
const db =
client.db("authDemo");
users =
db.collection("users");
console.log(
"MongoDB connected."
);
}
// Login
app.post("/login", async (req, res) => {
try {
const {
username,
password
} = req.body;
const user =
await users.findOne({
username:
username
});
if (!user) {
return res.status(401).json({
message:
"Invalid username or password."
});
}
const passwordCorrect =
await bcrypt.compare(
password,
user.password
);
if (!passwordCorrect) {
return res.status(401).json({
message:
"Invalid username or password."
});
}
const token =
jwt.sign(
{
userId:
user._id.toString(),
username:
user.username
},
JWT_SECRET,
{
expiresIn:
"1h"
}
);
res.json({
message:
"Login successful.",
token:
token
});
} catch (error) {
console.error(
error.message
);
res.status(500).json({
message:
"Login failed."
});
}
});
// Authentication middleware
function authenticateToken(
req,
res,
next
) {
const authHeader =
req.headers.authorization;
const token =
authHeader &&
authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({
message:
"Authentication token required."
});
}
try {
const user =
jwt.verify(
token,
JWT_SECRET
);
req.user =
user;
next();
} catch (error) {
return res.status(403).json({
message:
"Invalid or expired token."
});
}
}
// Protected route
app.get(
"/profile",
authenticateToken,
async (req, res) => {
try {
const user =
await users.findOne({
username:
req.user.username
});
if (!user) {
return res.status(404).json({
message:
"User not found."
});
}
res.json({
username:
user.username
});
} catch (error) {
res.status(500).json({
message:
"Unable to load profile."
});
}
}
);
// Start server
async function startServer() {
try {
await connectDatabase();
app.listen(3000, () => {
console.log(
"Server running on port 3000"
);
});
} catch (error) {
console.error(
error.message
);
}
}
startServer();
Example MongoDB User
Your users collection can contain a document similar to:
{
"username": "rahul",
"password": "$2b$10$..."
}
The password value should be an actual bcrypt hash generated when the user registered.
Login Request
POST /login
Body:
{
"username": "rahul",
"password": "12345"
}
Login Response
{
"message": "Login successful.",
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Protected Request
GET /profile
Header:
Authorization: Bearer YOUR_TOKEN
Important Point
The JWT should contain only information needed by the application. Do not put passwords or other sensitive secrets inside the token payload.
Question 10: How do you build a complete JWT authentication API in Node.js?
Problem
Build a beginner-friendly authentication API with:
- User registration
- Password hashing
- Login
- JWT creation
- JWT verification
- Authentication middleware
- Protected profile route
- MongoDB
Solution
Install the required packages:
npm init -y
npm install express mongodb bcrypt jsonwebtoken
Create index.js:
const express = require("express");
const {
MongoClient
} = require("mongodb");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const uri =
process.env.MONGODB_URI ||
"mongodb://127.0.0.1:27017";
const JWT_SECRET =
process.env.JWT_SECRET ||
"development-secret-change-this";
const client =
new MongoClient(uri);
let users;
// MongoDB connection
async function connectDatabase() {
await client.connect();
const db =
client.db("jwtAuthDemo");
users =
db.collection("users");
console.log(
"MongoDB connected successfully."
);
}
// Register
app.post(
"/register",
async (req, res) => {
try {
const {
username,
password
} = req.body;
if (
!username ||
!password
) {
return res.status(400).json({
success: false,
message:
"Username and password are required."
});
}
const existingUser =
await users.findOne({
username:
username
});
if (existingUser) {
return res.status(409).json({
success: false,
message:
"Username already exists."
});
}
const hashedPassword =
await bcrypt.hash(
password,
10
);
await users.insertOne({
username:
username,
password:
hashedPassword
});
res.status(201).json({
success: true,
message:
"Registration successful."
});
} catch (error) {
console.error(
error.message
);
res.status(500).json({
success: false,
message:
"Registration failed."
});
}
}
);
// Login
app.post(
"/login",
async (req, res) => {
try {
const {
username,
password
} = req.body;
const user =
await users.findOne({
username:
username
});
if (!user) {
return res.status(401).json({
success: false,
message:
"Invalid username or password."
});
}
const passwordCorrect =
await bcrypt.compare(
password,
user.password
);
if (!passwordCorrect) {
return res.status(401).json({
success: false,
message:
"Invalid username or password."
});
}
const token =
jwt.sign(
{
userId:
user._id.toString(),
username:
user.username
},
JWT_SECRET,
{
expiresIn:
"1h"
}
);
res.json({
success: true,
message:
"Login successful.",
token:
token
});
} catch (error) {
console.error(
error.message
);
res.status(500).json({
success: false,
message:
"Login failed."
});
}
}
);
// JWT authentication middleware
function authenticateToken(
req,
res,
next
) {
const authHeader =
req.headers.authorization;
const token =
authHeader &&
authHeader.startsWith(
"Bearer "
)
? authHeader.slice(7)
: null;
if (!token) {
return res.status(401).json({
success: false,
message:
"Authentication token required."
});
}
try {
const decoded =
jwt.verify(
token,
JWT_SECRET
);
req.user =
decoded;
next();
} catch (error) {
return res.status(403).json({
success: false,
message:
"Invalid or expired token."
});
}
}
// Protected profile route
app.get(
"/profile",
authenticateToken,
async (req, res) => {
try {
const user =
await users.findOne({
_id:
new require("mongodb")
.ObjectId(
req.user.userId
)
});
if (!user) {
return res.status(404).json({
success: false,
message:
"User not found."
});
}
res.json({
success: true,
profile: {
username:
user.username
}
});
} catch (error) {
console.error(
error.message
);
res.status(500).json({
success: false,
message:
"Unable to load profile."
});
}
}
);
// Start server
async function startServer() {
try {
await connectDatabase();
app.listen(3000, () => {
console.log(
"Server running at http://localhost:3000"
);
});
} catch (error) {
console.error(
"Server startup failed:",
error.message
);
}
}
startServer();
A Cleaner Import for ObjectId
For better readability, you can import ObjectId together with MongoClient:
const {
MongoClient,
ObjectId
} = require("mongodb");
Then change the profile query to:
const user =
await users.findOne({
_id:
new ObjectId(
req.user.userId
)
});
This is the recommended version for the example.
Step 1: Register a User
Send:
POST http://localhost:3000/register
Body:
{
"username": "rahul",
"password": "mySecret123"
}
Response:
{
"success": true,
"message": "Registration successful."
}
Step 2: Login
Send:
POST http://localhost:3000/login
Body:
{
"username": "rahul",
"password": "mySecret123"
}
Response:
{
"success": true,
"message": "Login successful.",
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Step 3: Copy the JWT
Copy the value of:
token
You will use it to access the protected route.
Step 4: Access Profile
Send:
GET http://localhost:3000/profile
Add this header:
Authorization: Bearer YOUR_TOKEN
Response
{
"success": true,
"profile": {
"username": "rahul"
}
}
Complete JWT Authentication Flow
REGISTER
↓
Receive Password
↓
bcrypt.hash()
↓
Save User
↓
MongoDB
LOGIN
↓
Find User
↓
bcrypt.compare()
↓
Password Valid?
↙ ↘
YES NO
↓ ↓
Create JWT 401 Error
↓
Return JWT Token
↓
Client
PROTECTED ROUTE
↓
Authorization: Bearer TOKEN
↓
Authentication Middleware
↓
jwt.verify()
↓
Token Valid?
↙ ↘
YES NO
↓ ↓
next() 403 Error
↓
Protected Route
↓
Response
Important Security Notes
The examples in this chapter are designed for learning. Before using JWT authentication in a real application:
- Use HTTPS.
- Store JWT secrets in environment variables.
- Use strong, unpredictable secrets.
- Never store plain-text passwords.
- Hash passwords with an appropriate password-hashing algorithm.
- Do not place passwords or private information inside JWT payloads.
- Validate request data.
- Use reasonable token expiration times.
- Protect login and registration endpoints against abuse.
- Consider secure cookie-based token storage where appropriate for your application architecture.
- Implement authorization separately from authentication.
- Keep dependencies updated.
- Do not expose internal database or authentication errors to users.
- Consider refresh-token strategies when long-lived authentication is required.
Key Takeaways
- JWT stands for JSON Web Token.
- JWT is commonly used for authentication in Node.js APIs.
- The
jsonwebtokenpackage can create and verify JWTs. jwt.sign()creates a token.jwt.verify()verifies a token.- JWTs contain a payload and a cryptographic signature.
- JWT payloads are not a safe place for passwords or secrets.
expiresIncan be used to give a token an expiration time.- A client commonly sends a JWT using the
Authorizationheader. - The common format is
Authorization: Bearer TOKEN. - Express middleware can verify JWTs before protected routes execute.
req.headers.authorizationcan be used to read the Authorization header.req.usercan store verified user information for later middleware or route handlers.next()passes control to the next Express middleware or route handler.- HTTP
401is commonly used when authentication credentials are missing or invalid. - HTTP
403can be used when a supplied credential is not accepted for the requested access. - Authentication determines who a user is.
- Authorization determines what that user is allowed to access.
- JWT and bcrypt have different purposes.
- bcrypt is used to securely hash passwords.
- JWT can be issued after successful credential verification.
- MongoDB can store users and their password hashes.
- A user’s password should never be stored as plain text.
- Login credentials should be checked before issuing a JWT.
- Protected routes should verify the JWT before returning protected information.
- JWT secrets should normally be stored in environment variables.
- HTTPS should be used when transmitting authentication credentials or tokens.
- Token expiration can reduce the impact of a leaked token.
- Authentication middleware can be reused across multiple protected routes.
- JWT authentication is commonly used when building REST APIs with Node.js and Express.js.
- A production authentication system requires more security controls than the basic examples shown here.
FAQs
1. What is JWT authentication in Node.js?
JWT authentication is a method of authenticating users with a signed JSON Web Token.
After successful login, the server creates a token. The client sends the token when accessing protected resources.
Login
↓
Verify User
↓
Create JWT
↓
Client Receives Token
↓
Client Sends Token
↓
Server Verifies Token
2. How do you create a JWT in Node.js?
Install the jsonwebtoken package:
npm install jsonwebtoken
Then use:
const jwt =
require("jsonwebtoken");
const token =
jwt.sign(
{
username: "rahul"
},
secretKey,
{
expiresIn: "1h"
}
);
The resulting value is the JWT.
3. How do you verify a JWT in Node.js?
Use jwt.verify():
const decoded =
jwt.verify(
token,
secretKey
);
If the token is valid, the decoded payload is returned.
If the token is invalid or expired, verification fails.
4. Where should a JWT be sent in an API request?
A common method is the Authorization header:
Authorization: Bearer YOUR_TOKEN
For example:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
The Node.js server can read this value using:
req.headers.authorization
5. What is JWT authentication middleware in Express.js?
JWT authentication middleware is an Express middleware function that checks a token before allowing a request to continue.
For example:
app.get(
"/profile",
authenticateToken,
(req, res) => {
res.json({
message: "Profile"
});
}
);
The middleware can verify the token and call:
next();
when the request is authenticated.
6. Is JWT the same as password hashing?
No.
JWT and password hashing have different purposes.
bcrypt
↓
Protect stored passwords
JWT
↓
Represent an authenticated request/session
A typical login process can use both:
User Password
↓
bcrypt.compare()
↓
Password Correct
↓
Create JWT
7. Is JWT authentication secure?
JWT can be used securely, but simply using JWT does not automatically make an application secure.
A secure implementation should use HTTPS, strong secrets or appropriate signing keys, short-lived tokens where appropriate, secure token handling, password hashing, input validation, and proper authorization checks.
Written by Shubhranshu Shekhar, who has trained 20000+ students in coding.
