Currently, I have a URL for authentication sent via user email, which includes the following token: "http://localhost:5173/activation/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWZkNmM0OTMyOWI4YzhjNjFjMjJiY2YiLCJpYXQiOjE3MTExMDcxNDUsImV4cCI6MTcxMTE5MzU0NX0.BLkEkRtRxYk3uKKobWRuD0rsf7qob35l5-OmdJfRqyU"
The problem at hand is that I'm trying to get the token (the string after "activation/") using useParams()
of React-Router-DOM. If that string does not include characters like "."
then I get "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
but if a full token is like above like "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWZkNmM0OTMyOWI4YzhjNjFjMjJiY2YiLCJpYXQiOjE3MTExMDcxNDUsImV4cCI6MTcxMTE5MzU0NX0.BLkEkRtRxYk3uKKobWRuD0rsf7qob35l5-OmdJfRqyU"
, then I don't get the complete token value.
routes/auth.js
const express = require("express");
const authController = require("../controllers/auth");
const router = express.Router();
router.post("/signup", authController.postSignup);
router.post("/activation", authController.activateAccount);
module.exports = router;
controllers/auth.js
const bcrypt = require("bcrypt");
const User = require("../models/user");
const { v4: uuidv4 } = require("uuid");
const jwt = require("jsonwebtoken");
const { getAuth } = require("firebase-admin/auth");
const emailRegex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; // regex for email
const passwordRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/; // regex for password
const sendMail = require("../utils/sendMail");
const generateUserName = async (email) => {
let username = email.split("@")[0];
const isUsernameExists = await User.exists({
"personal_info.username": username
});
if (isUsernameExists) {
username += uuidv4().substring(0, 5);
}
return username;
};
const fortmatDataToSend = (user) => {
const access_token = jwt.sign(
{ id: user._id },
process.env.SECRET_ACCESS_KEY,
{ expiresIn: "1h" }
);
return {
access_token,
_id: user._id,
profile_img: user.personal_info.profile_img,
username: user.personal_info.username,
fullname: user.personal_info.fullname
};
};
exports.postSignup = async (req, res) => {
const { fullname, email, password } = req.body;
try {
if (fullname.length < 3) {
return res
.status(403)
.json({ error: "Fullname must be at least 3 letters long !" });
}
if (!email.length) {
return res.status(403).json({ error: "Enter Email" });
}
if (!emailRegex.test(email)) {
return res.status(403).json({ error: "Invalid email address" });
}
if (!passwordRegex.test(password)) {
return res.status(403).json({
error:
"Password should be 6 to 20 characters long with a numeric, 1 lowercase and 1 uppercase letters!"
});
}
const existingUser = await User.findOne({ email: email }).exec();
if (existingUser) {
return res.status(409).json({ error: "Email is already registered" });
}
const hashedPassword = await bcrypt.hash(password, 12);
const username = await generateUserName(email);
const user = new User({
personal_info: {
fullname,
email,
password: hashedPassword,
username,
active: false
}
});
const activation_token = jwt.sign(
{ userId: user._id },
process.env.SECRET_ACCESS_KEY,
{ expiresIn: "1d" }
);
const activationLink = `${process.env.CLIENT}/activation/${activation_token}`;
await sendMail({
email: email,
subject: "Activate Your Account",
message: `
<p>Hello ${fullname} 👋, Please click following link to active your account ⬇️</p>
<a href="${activationLink}">Verify Your Email</a>
`
});
await user.save();
return res.status(200).json({
status:
"Registration successful. Please check your email for activation link."
});
} catch (err) {
if (err.code === 11000) {
return res.status(409).json({ error: "Email is already registered!" });
}
return res.status(500).json({ error: err.message });
}
};
exports.activateAccount = async (req, res) => {
try {
const { activation_token } = req.body;
const decodedToken = jwt.verify(
activation_token,
process.env.SECRET_ACCESS_KEY
);
const userId = decodedToken.userId;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ error: "User not found" });
}
if (user.personal_info.active) {
return res.status(400).json({ error: "Account already activated" });
}
user.personal_info.active = true;
await user.save();
return res.status(200).json({ message: "Account activated successfully" });
} catch (error) {
if (error.name === "TokenExpiredError") {
return res.status(400).json({ error: "Activation token expired" });
} else if (error.name === "JsonWebTokenError") {
return res.status(400).json({ error: "Invalid activation token" });
}
return res.status(500).json({ error: "Internal server error" });
}
};
⬇️Frontend code
App.js
import { Route, Routes } from "react-router-dom";
import Navbar from "./components/navbar.component";
import UserAuthForm from "./pages/userAuthForm";
import UserContextProvider from "../context/user-context";
import Editor from "./pages/editor";
import Activation from "./pages/Activation";
const App = () => {
return (
<UserContextProvider>
<Routes>
<Route path="/" element={<Navbar />}>
<Route path="signin" element={<UserAuthForm type="signin" />}></Route>
<Route path="signup" element={<UserAuthForm type="signup" />}></Route>
</Route>
<Route path="/editor" element={<Editor />} />
<Route path="/activation/:activation_token" element={<Activation />} />
</Routes>
</UserContextProvider>
);
};
export default App;
Activation.jsx
import axios from "axios";
import { useEffect, useState } from "react";
import { useLocation, useParams } from "react-router-dom";
const Activation = () => {
const { activation_token } = useParams();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (activation_token) {
const activateAccount = async () => {
try {
await axios.post(
`http://localhost:3000/activation/${activation_token}`
);
} catch (error) {
setError(true);
setLoading(false);
}
};
activateAccount();
}
}, [activation_token]);
return (
<div>
{loading ? (
<p>Loading...</p>
) : error ? (
<div>
<h1>Activation Error</h1>
<p>
There was an error activating your account. Please try again later.
</p>
</div>
) : (
<div>
<h1>Account Activated Successfully</h1>
<p>Your account has been successfully activated.</p>
</div>
)}
</div>
);
};
export default Activation;
I don't know where the problem comes from. I'm trying to get the token (string after "activation/"
) using useParams()
, but if that string doesn't include characters like "."
then I get (e.g. "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
) but if the full token is as above, I don't get it. I have console.log(activation_token)
taken from useParams
.
As you have found here some characters are not valid in the path part of a URL, so when you use a token value as a path segment not all of it is readable. You can either sanitize your generated tokens so they only contain valid URL path characters, or you can move the token to the search parameters.
Example URL: "http://localhost:5173/activation?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWZkNmM0OTMyOWI4YzhjNjFjMjJiY2YiLCJpYXQiOjE3MTExMDcxNDUsImV4cCI6MTcxMTE5MzU0NX0.BLkEkRtRxYk3uKKobWRuD0rsf7qob35l5-OmdJfRqyU"
const App = () => {
return (
<UserContextProvider>
<Routes>
<Route path="/" element={<Navbar />}>
<Route path="signin" element={<UserAuthForm type="signin" />}></Route>
<Route path="signup" element={<UserAuthForm type="signup" />}></Route>
</Route>
<Route path="/editor" element={<Editor />} />
<Route path="/activation" element={<Activation />} />
</Routes>
</UserContextProvider>
);
};
Use the useSearchParams
hook to access the token search parameter in the queryString.
import axios from "axios";
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
const Activation = () => {
const [searchParams] = useSearchParams();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(true);
const activation_token = searchParams.get("token");
useEffect(() => {
if (activation_token) {
const activateAccount = async () => {
setLoading(true);
try {
await axios.post(
`http://localhost:3000/activation/${activation_token}`
);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
};
activateAccount();
}
}, [activation_token]);
return (
<div>
{loading ? (
<p>Loading...</p>
) : error ? (
<div>
<h1>Activation Error</h1>
<p>
There was an error activating your account. Please try again later.
</p>
</div>
) : (
<div>
<h1>Account Activated Successfully</h1>
<p>Your account has been successfully activated.</p>
</div>
)}
</div>
);
};
export default Activation;