Learn how to implement secure JWT-based authentication in Node.js using access and refresh tokens. This guide covers user login, token generation, token refresh flow, and best practices for managing sessions in modern web and mobile applications.

In modern web applications, authentication plays a crucial role in securing user data and managing user sessions. One of the most popular techniques for implementing secure authentication is by using JWT-based (JSON Web Token) authentication. In this guide, we'll focus on implementing authentication using Access Tokens and Refresh Tokens in NodeJS.
JWTs are stateless tokens that are digitally signed and provide a secure way to verify user identity without needing to store session data on the server. This makes JWT-based authentication lightweight, scalable, and a great fit for distributed systems like REST APIs and microservices.
During the login process, after the user provides valid credentials (like username and password), we issue both an access token and a refresh token.
Here’s an example of how to generate and send both tokens after successful login:
export const loginUser = async (req: Request, res: Response) => {
try {
const { login_id, password } = req.body;
// Find the user by email or username
const user = await UserModel.findOne({
$or: [{ email_address: login_id }, { username: login_id }],
});
if (!user) {
return res.status(404).json({ error: "User not found" });
}
// Verify password
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return res.status(401).json({ error: "Invalid password" });
}
// Token payload with user information
const tokenDetails = {
userId: user._id,
username: user.username,
user_role: user.user_role,
};
// Generate access token (valid for 1 day)
const accessToken = jwt.sign(tokenDetails, process.env.SECRET_KEY || "", {
expiresIn: "1d",
});
// Generate refresh token (valid for 7 days)
const refreshToken = jwt.sign(
tokenDetails,
process.env.REFRESH_SECRET_KEY || "",
{ expiresIn: "7d" }
);
// Send tokens in response (or cookies)
res.cookie("accessToken", accessToken, {
secure: true,
httpOnly: true,
sameSite: "none",
});
res.cookie("refreshToken", refreshToken, {
secure: true,
httpOnly: true,
sameSite: "none",
});
res.status(200).json({ message: "Login successful", accessToken, refreshToken });
} catch (error) {
console.error("Error during login:", error);
res.status(500).json({ error: "Internal server error" });
}
};In this code: After successfully verifying the user’s credentials, we generate an Access Token that expires in 1 day and a Refresh Token that expires in 7 days.
Both tokens are sent back in HTTP-only cookies for security or can be included in the response for mobile applications.
Once the access token expires, we don't want to force users to log in again. Instead, the refresh token is used to issue a new access token.
Here’s how we handle that:
export const refreshToken = (req: Request, res: Response) => {
try {
// Get refresh token from cookies or headers
const refreshToken = req.cookies.refreshToken || req.headers["refresh-token"];
if (!refreshToken) {
return res.status(400).json({ error: "Refresh token not provided" });
}
// Verify refresh token
jwt.verify(
refreshToken,
process.env.REFRESH_SECRET_KEY || "",
(err: any, decoded: any) => {
if (err) {
return res.status(403).json({ error: "Invalid refresh token" });
}
// Generate new access token
const accessToken = jwt.sign(
{ userId: decoded.userId, username: decoded.username },
process.env.SECRET_KEY || "",
{ expiresIn: "15m" } // Shorter lifespan (15 mins)
);
// Send the new access token in cookies or headers
res.cookie("accessToken", accessToken, {
secure: true,
httpOnly: true,
sameSite: "none",
});
res.status(200).json({ message: "Access token refreshed successfully" });
}
);
} catch (error) {
console.error("Error refreshing access token:", error);
res.status(500).json({ error: "Internal server error" });
}
};In this refresh token flow:
JWT-based authentication with access and refresh tokens allows for secure, scalable, and stateless user authentication in NodeJS. By issuing short-lived access tokens and long-lived refresh tokens, you can ensure user sessions remain secure while minimizing the need for frequent logins.
This token-based system is an excellent foundation for building modern, secure applications.
New tutorials, book updates, and behind-the-scenes notes from the studio. No schedule, no spam.