So I have jwt authentication with access and refresh tokens. But the problem is when the access token expires and i generate a new access token on the frontend (react). It gives me a promise
backend/routes/auth.js (Route to refresh token and where im generating the refresh token and storing it (login route))
router.post("/login", async (req, res) => {
try {
// Validating user
const { error } = loginValidation(req.body);
if (error) return res.status(400).json(error.details[0].message);
// Making sure email is correct
const user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).json("Invalid email or password");
// Making sure the password is correct
const validPass = await bcrypt.compare(req.body.password, user.password);
if (!validPass) return res.status(400).json("Invalid email or password");
const userPayload = {
_id: user._id,
displayname: user.displayname,
username: user.username,
email: user.email,
bookmarkedTweets: user.bookmarkedTweets,
likedTweets: user.likedTweets,
followers: user.followers,
following: user.following,
date: user.date,
month: user.month,
day: user.day,
year: user.year,
profilePic: user.profilePic,
};
// Generating access token
const accessToken = generateAccessToken(userPayload);
// Generating refresh token
const refreshToken = jwt.sign(
user.toJSON(),
process.env.REFRESH_TOKEN_SECRET
);
// Getting info for refresh token
const newRefreshToken = new RefreshToken({
refreshToken: refreshToken,
});
// Saving refresh token into db
await newRefreshToken.save();
res.json({
_id: user._id,
displayname: user.displayname,
username: user.username,
email: user.email,
password: user.password,
bookmarkedTweets: user.bookmarkedTweets,
likedTweets: user.likedTweets,
followers: user.followers,
following: user.following,
date: user.date,
month: user.month,
day: user.day,
year: user.year,
profilePic: user.profilePic,
accessToken: accessToken,
refreshToken: refreshToken,
});
} catch (error) {
res.sendStatus(500);
console.log(error);
}
});
router.post("/refresh/token", async (req, res) => {
try {
// Getting refresh token
const refreshToken = req.body.token;
// Finding refresh token
const _refreshToken = await RefreshToken.findOne({ refreshToken: refreshToken });
// Making sure there is a refresh token and that refresh token exists in db
if (refreshToken == null) return res.sendStatus(401);
if (!_refreshToken) return res.sendStatus(403);
// Vaifying refresh token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
const userPayload = {
_id: user._id,
displayname: user.displayname,
username: user.username,
email: user.email,
bookmarkedTweets: user.bookmarkedTweets,
likedTweets: user.likedTweets,
followers: user.followers,
following: user.following,
month: user.month,
day: user.day,
year: user.year,
date: user.date,
profilePic: user.profilePic,
};
// Generating access token
const accessToken = generateAccessToken(userPayload);
res.json({ accessToken });
});
} catch (error) {
res.sendStatus(500);
}
});
backend/middlewares/authenticateToken.js
const jwt = require("jsonwebtoken");
require("dotenv").config();
module.exports = function authenticateToken(req, res, next) {
const authHeader = req.headers["authorization"]; // Getting auth header
const token = authHeader && authHeader.split(" ")[1]; // Getting access token from auth header
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Making sure the token is valid
req.user = user;
next();
});
};
backend/utils/generateAccessToken.js
const jwt = require("jsonwebtoken");
require("dotenv").config();
module.exports = function (user) {
return jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: "15s" });
};
frontend/src/api/axios.js
import axios from "axios";
const BASE_URL = "http://localhost:5000";
const accessToken = sessionStorage.getItem("accessToken");
const refreshToken = localStorage.getItem("refreshToken");
export default axios.create({
baseURL: BASE_URL,
headers: { "Content-Type": "application/json" },
});
const client = axios.create({ baseURL: BASE_URL });
export const axiosAuth = async ({ ...options }) => {
// Sending access token with request
client.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
const onSuccess = (response) => response;
const one rror = (error) => {
if (error.response.status === 403) {
// Making function to generate new access token
const refresh = async () => {
const { data } = await axios.post(`${BASE_URL}/auth/refresh/token`, {
token: refreshToken,
});
return data;
};
// Generating access token
const newAccessToken = refresh();
sessionStorage.setItem("accessToken", newAccessToken);
return client({ ...options });
}
throw error;
};
return client(options).then(onSuccess).catch(onError);
};
frontend/src/modals/Tweet.modal.js (Where im using axiosAuth)
import React, { useState } from "react";
import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import PublicOutlinedIcon from "@mui/icons-material/PublicOutlined";
import CollectionsOutlinedIcon from "@mui/icons-material/CollectionsOutlined";
import GifBoxOutlinedIcon from "@mui/icons-material/GifBoxOutlined";
import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined";
import SentimentSatisfiedAltOutlinedIcon from "@mui/icons-material/SentimentSatisfiedAltOutlined";
import ScheduleOutlinedIcon from "@mui/icons-material/ScheduleOutlined";
import LocationOnOutlinedIcon from "@mui/icons-material/LocationOnOutlined";
import Loader from "../components/Loader/Loader.comp";
import { useDispatch, useSelector } from "react-redux";
import {
setTweetModal,
setTweetPending,
tweetErrorClear,
tweetFail,
} from "../features/tweet.slice";
import { axiosAuth } from "../api/axios";
function Tweet() {
const [textfield, setTextfield] = useState("");
const { user } = useSelector((state) => state.user);
const { tweetModalIsOpen, error, isLoading } = useSelector(
(state) => state.tweet
);
const { profilePic } = user;
const dispatch = useDispatch();
if (error !== "") {
setTimeout(() => dispatch(tweetErrorClear()), 3000);
}
const handleOnClick = async () => {
dispatch(setTweetPending(true));
try {
await axiosAuth({
url: "/posts/create",
method: "POST",
body: { textfield },
});
} catch (error) {
dispatch(tweetFail(error.response.data));
}
};
return (
<div
className={
tweetModalIsOpen
? `h-screen w-screen absolute inset-0 bg-white py-2 px-4`
: `hidden`
}
>
<div className="flex-items justify-between">
<div
className="p-1 cursor-pointer rounded-full hover:bg-gray-200 transition-color"
onClick={() => dispatch(setTweetModal(false))}
>
<ArrowBackIcon style={{ fontSize: "1.5rem" }} />
</div>
<button
className="py-1 px-4 transition-color rounded-full bg-blue-500 text-white font-bold hover:bg-blue-600"
onClick={() => handleOnClick()}
>
{isLoading ? <Loader forPage={false} /> : <h1>Tweet</h1>}
</button>
</div>
<div className="flex mt-6 space-x-2">
{profilePic === "" ? (
<AccountCircleOutlinedIcon style={{ fontSize: "2rem" }} />
) : (
<img src={profilePic} alt="profile_pic" />
)}
<div>
<textarea
name="tweet"
id="tweet"
rows="4"
value={textfield}
onChange={(e) => setTextfield(e.target.value)}
className="w-screen outline-none text-xl text-gray-700 placeholder:text-gray-600"
placeholder="What's happening?"
></textarea>
<div className="flex-items space-x-2 text-blue-400 font-bold py-0.5 px-2 rounded-full hover:bg-blue-50 transition-color cursor-pointer w-max">
<PublicOutlinedIcon style={{ fontSize: "1.5rem" }} />
<h1>Everyone can reply</h1>
</div>
<div className="ring-1 ring-gray-100 my-2 w-[75%]" />
<div className="flex-items space-x-2 text-blue-500">
<div className="tweet-icon">
<CollectionsOutlinedIcon style={{ fontSize: "1.5rem" }} />
</div>
<div className="tweet-icon">
<GifBoxOutlinedIcon style={{ fontSize: "1.5rem" }} />
</div>
<div className="tweet-icon">
<BarChartOutlinedIcon style={{ fontSize: "1.5rem" }} />
</div>
<div className="tweet-icon">
<SentimentSatisfiedAltOutlinedIcon
style={{ fontSize: "1.5rem" }}
/>
</div>
<div className="tweet-icon">
<ScheduleOutlinedIcon style={{ fontSize: "1.5rem" }} />
</div>
<div className="tweet-icon">
<LocationOnOutlinedIcon style={{ fontSize: "1.5rem" }} />
</div>
</div>
</div>
</div>
{error && <h1 className="error err-animation">{error}!</h1>}
</div>
);
}
export default Tweet;
Thanks in advance!
CodePudding user response:
I assume that _refreshToken
is the promise you refer to. In order to obtain the eventual value of that promise (that is, the refresh token itself), add async
to your middleware function and await
to the assignment statement:
router.post("/refresh/token", async (req, res) => {
try {
const refreshToken = req.body.token;
const _refreshToken = await RefreshToken.findOne({ refreshToken: refreshToken });
Same in frontend/src/api/axios.js
:
const one rror = async (error) => {
...
const newAccessToken = await refresh();