User registration
First thing first we need to register a user
- Destucturing the received username & password from request.
- Check for duplicate username in the db
- Encrypt the password using encrypt package
- Create and save new user into db
- Send proper status and make error handling
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ "message": 'Username and password are required.'});
const duplicate = await User.findOne({ username: username }).exec();
if (duplicate) return res.sendStatus(409); // Conflict
try {
// encrypt the password
const hashedPassword = await bcrypt.hash(password, 10);
// create and store the new user
const result = await User.create({
"username": username,
"password": hashedPassword
});
res.status(201).json({ 'success': `New user ${username} created`});
} catch (err) {
res.status(500).json({ "message": err.message });
}
User authorization
After we registrate user, we also need to get username/password from request.body and cookies (content from it)
- Find user by username in db 1.1 If user is not found => 401 | Unathorized
- Compare password using bcrypt package
- Create AT (Access Token) & RT (Refresh Token). Tokens contains (payload, SECRET, options).
- If user came in without RT inside cookies, array of RTs stays unchanged, if cookies contains something we save array without that RT. (This supports multiple devices).
- Clear all cookie before we give new tokens
- Update array of tokens inside db
- User gets AT & RT
const cookies = req.cookies;
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ "message": 'Username and password are required' });
const foundUser = await User.findOne({ username: username }).exec();
if (!foundUser) return res.sendStatus(401);
const matchPassword = await bcrypt.compare(password, foundUser.password);
if (matchPassword) {
const roles = Object.values(foundUser.roles);
const accessToken = jwt.sign(
{
"UserInfo": {
"username": foundUser.username,
"roles": roles
}
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '30s' }
);
const newRefreshToken = jwt.sign(
{ "username": foundUser.username },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '1d' }
);
const newRefreshTokenArray =
!cookies?.jwt
? foundUser.refreshToken
: foundUser.refreshToken.filter(rt => rt !== cookies.jwt);
if (cookies?.jwt) {
res.clearCookie('jwt', { httpOnly: true, sameSite: 'None', secure: true });
}
foundUser.refreshToken = [...newRefreshTokenArray, newRefreshToken];
const result = await foundUser.save();
res.cookie('jwt', newRefreshToken, { httpOnly: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000 });
res.json({ accessToken });
} else {
res.sendStatus(401);
}
Update AT by RT
- Clear user cookie
- Find user by RT in db
- If user is not found in db, user is hacked. We intentionally clear array of tokens.
- Create new array without received RT from cookie
- Verify RT and give new tokens, then save it into db
- Send cookie and result
const cookies = req.cookies;
if (!cookies?.jwt) return res.sendStatus(401);
const refreshToken = cookies.jwt;
res.clearCookie('jwt', { httpOnly: true, sameSite: 'None', secure: true });
const foundUser = await User.findOne({ refreshToken: refreshToken }).exec();
if (!foundUser) {
jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET,
async (err, decoded) => {
if (err) return res.sendStatus(403);
const hackedUser = await User.findOne({ username: decoded.username }).exec();
hackedUser.refreshToken = [];
const result = await hackedUser.save();
console.log(result);
}
);
return res.sendStatus(403) // Forbidden
};
const newRefreshTokenArray = foundUser.refreshToken.filter(rt => rt !== refreshToken);
jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET,
async (err, decoded) => {
if (err) { // if RT has been expired
foundUser.refreshToken = [...newRefreshTokenArray];
const result = await foundUser.save();
return res.sendStatus(403);
}
if (err || foundUser.username !== decoded.username) return res.sendStatus(403);
const roles = Object.values(foundUser.roles);
const accessToken = jwt.sign(
{
"UserInfo": {
"username": decoded.username,
"roles": roles
}
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '30s' },
);
const newRefreshToken = jwt.sign(
{ "username": foundUser.username },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '1d' }
);
foundUser.refreshToken = [...newRefreshTokenArray, newRefreshToken];
const result = await foundUser.save();
res.cookie('jwt', newRefreshToken, { httpOnly: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000 });
res.json({ accessToken });
}
);
User logout
- Get RT from cookies
- Find user by RT inside db. (if user is not found, clear cookie)
- Delete RT in db
- Clear cookie
const cookies = req.cookies;
if (!cookies?.jwt) return res.sendStatus(204);
const refreshToken = cookies.jwt;
const foundUser = await User.findOne({refreshToken: refreshToken}).exec();
if (!foundUser) {
res.clearCookie('jwt', {
httpOnly: true,
sameSite: 'None',
secure: true
});
return res.sendStatus(204);
}
foundUser.refreshToken = foundUser.refreshToken.filter(rt => rt !== refreshToken);
const result = await foundUser.save();
console.log(result);
res.clearCookie('jwt', { httpOnly: true, sameSite: 'None', secure: true }); // secure: true - only serves on https (it's on production)
res.sendStatus(204);
Top comments (0)