Reputation: 11
I have been working with user authentication in my application; access token life span is 30 minutes and refresh token life span is 7 days. The thing I would like to achieve: in case an access token expires after a user was already active on the website, it needs to auto-renew in the background without pointing it out to the user, so that he or she can continue seamless completion of his or her tasks.
How to track whether access token has expired and refresh it with refresh token where necessary? It's Node.js backend and React.js frontend.
I want to update the token in the background without notifying the user or changing any page. All tasks should happen in the background. Also, if the user closes the page and later returns, the token should be refreshed when they arrive
this backends code userAuth.js
const User = require("../../models/User");
const OTP = require("../../models/otp");
const bcrypt = require("bcryptjs");
const crypto = require("crypto");
const {
generateAccessToken,
generateRefreshToken,
verifyRefreshToken,
} = require("../../util/jwtUtile");
require("dotenv").config();
const nodemailer = require("nodemailer");
// Configure nodemailer
const transporter = nodemailer.createTransport({
service: "Gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
// Generate numeric OTP
const generateOTP = () => {
return Math.floor(100000 + Math.random() * 900000).toString(); // Generates a 6-digit numeric OTP
};
// Registration
exports.registerUser = async (req, res) => {
const { username, email, password } = req.body;
try {
const hashedPassword = await bcrypt.hash(password, 10);
const otp = generateOTP();
// Store OTP in the database
const otpEntry = new OTP({ email, otp });
await otpEntry.save();
// Send OTP to user's email
const mailOptions = {
from: process.env.EMAIL_USER,
to: email,
subject: "Your OTP for Registration",
text: `Your OTP is ${otp}`,
};
await transporter.sendMail(mailOptions);
res.status(201).json({ msg: "OTP sent to your email" });
} catch (error) {
console.error("Error:", error.message);
res.status(400).json({ error: error.message });
}
};
// Verify OTP
exports.verifyOTP = async (req, res) => {
const { email, otp } = req.body;
try {
const otpEntry = await OTP.findOne({ email, otp });
if (!otpEntry) {
return res.status(400).json({ msg: "Invalid OTP or email" });
}
// OTP is correct, complete the registration
const storedData = otpEntry;
const newUser = new User({
username: storedData.username,
email: storedData.email,
password: storedData.password,
});
const user = await newUser.save();
await OTP.deleteOne({ email, otp }); // Remove the OTP entry
console.log("User registered successfully");
res.status(201).json({ msg: "User registered successfully", user });
} catch (error) {
console.error("Error:", error.message);
res.status(400).json({ error: error.message });
}
};
// Login
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ msg: "UserId or password is invalid" });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ msg: "UserId or password is invalid" });
}
const accessToken = generateAccessToken({
id: user._id,
isAdmin: user.isAdmin,
});
const refreshToken = generateRefreshToken({
id: user._id,
isAdmin: user.isAdmin,
});
res.cookie("accessToken", accessToken, { httpOnly: true, secure: true });
res.cookie("refreshToken", refreshToken, { httpOnly: true });
res.status(200).json({ msg: "Login successful" });
} catch (error) {
console.error("Error:", error.message);
res.status(500).json({ error: error.message });
}
};
and refreshToken.js
const { generateAccessToken, verifyRefreshToken } = require("./jwtUtile");
// Refresh Token
exports.refreshToken = (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken)
return res.status(403).json({ error: "No refresh token provided" });
try {
const user = verifyRefreshToken(refreshToken);
const newAccessToken = generateAccessToken({
id: user.id,
isAdmin: user.isAdmin,
});
res.cookie("accessToken", newAccessToken, { httpOnly: true, secure: true });
} catch (err) {
return res
.status(401)
.json({ error: "Refresh token expired, please login again." });
}
};
Upvotes: 1
Views: 139
Reputation: 17477
Refresh token handling is part of the overall authentication procedure in the backend, outlined in the following flow chart (press "Run code snippet" to see the chart).
<script type="module" src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs"></script>
<pre class="mermaid">
flowchart TD
A[Receive request with cookies accessToken and refreshToken] --> B{Validate accessToken}
B -->|valid| C[Process request]
B -->|invalid| D{Validate refresh token}
subgraph refreshToken.js
D -->|valid| E[Issue new accessToken]
D -->|invalid| F[Error 'log in again']
end
E --> C
</pre>
A new refresh token may need to be issued together with the access token if the old one nears its expiry date.
Upvotes: 0