Reputation: 750
I was following a tutorial at Authentication in NodeJS With Express and Mongo - CodeLab #1
I got everything to work perfectly, but the tutorial does not address how to log out a user.
From what I can tell, the session is being saved on Mongoose Atlas, which is the database I am using. When I log a user in with Postman, I get a token back. But I am not sure how to configure the /logout route.
Here is my code:
//routes/user.js
const express = require("express");
const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const router = express.Router();
const auth = require("../middleware/auth");
const User = require("../models/User");
/**
* @method - POST
* @param - /signup
* @description - User SignUp
*/
//Signup
router.post(
"/signup",
[
check("username", "Please Enter a Valid Username")
.not()
.isEmpty(),
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const {
username,
email,
password
} = req.body;
try {
let user = await User.findOne({
email
});
if (user) {
return res.status(400).json({
msg: "User Already Exists"
});
}
user = new User({
username,
email,
password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString", {
expiresIn: 10000
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (err) {
console.log(err.message);
res.status(500).send("Error in Saving");
}
}
);
// Login
router.post(
"/login",
[
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { email, password } = req.body;
try {
let user = await User.findOne({
email
});
if (!user)
return res.status(400).json({
message: "User Not Exist"
});
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return res.status(400).json({
message: "Incorrect Password !"
});
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString",
{
expiresIn: 3600
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (e) {
console.error(e);
res.status(500).json({
message: "Server Error"
});
}
}
);
// router.route("/logout").get(function (req, res, next) {
// if (expire(req.headers)) {
// delete req.user;
// return res.status(200).json({
// "message": "User has been successfully logged out"
// });
// } else {
// return next(new UnauthorizedAccessError("401"));
// }
// });
router.get("/me", auth, async (req, res) => {
try {
// request.user is getting fetched from Middleware after token authentication
const user = await User.findById(req.user.id);
res.json(user);
} catch (e) {
res.send({ message: "Error in Fetching user" });
}
});
router.get('/logout', isAuthenticated, function (req, res) {
console.log('User Id', req.user._id);
User.findByIdAndRemove(req.user._id, function (err) {
if (err) res.send(err);
res.json({ message: 'User Deleted!' });
})
});
module.exports = router;
function isAuthenticated(req, res, next) {
console.log("req: " + JSON.stringify(req.headers.authorization));
// if (!(req.headers && req.headers.authorization)) {
// return res.status(400).send({ message: 'You did not provide a JSON web token in the authorization header' });
//}
};
///middleware/auth.js
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("token");
if (!token) return res.status(401).json({ message: "Auth Error" });
try {
const decoded = jwt.verify(token, "randomString");
req.user = decoded.user;
next();
} catch (e) {
console.error(e);
res.status(500).send({ message: "Invalid Token" });
}
};
///models/User.js
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now()
}
});
// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);
So my question is, how can I implement a /logout route so that if the user clicks the logout button and invokes that route, their token is destroyed. I am only asking about the back-end part. I can handle using axios.
Thanks.
Upvotes: 3
Views: 5764
Reputation: 1
Its a Simple Solution
const Logout = async (req, res, next) => {
res.cookie('cookie', '', {
expireIn: new Date(Date.now())
})
return res.status(200).json({ message: 'User Logout Successfully' })
}
Very Simple Way to Logout a User
Upvotes: 0
Reputation: 18929
From what I see, you are not saving any session data or storing tokens anywhere - which is great. You are simply appending the token to your headers in requests to the API.
So the only thing you can do is possibly expire the token in the /logout route
and then ensure you delete the token on the client - could be localStorage, sessionStorage etc - your client code needs to kill the token so it cannot be included again.
Side note:
You are not extending the token lifetime anywhere, so even if the user keeps interacting on the website, the token expiration is not being updated. You will need to manually refresh the token/generate a new token to have a sliding expiration.
I would suggest you save the token in cookies rather. Set the cookie to HttpOnly, Secure, and specify the domain. This is far more secure and will allow you to also expire the cookie from the API. If any scripts you include get compromised, they can access all your users’ tokens easily.
Example:
import {serialize} from 'cookie';
import jsend from 'jsend';
...
const token = jwt.sign(
{
id: validationResult.value.id // whatever you want to add to the token, here it is the id of a user
},
privateKeyBuffer,
{
expiresIn: process.env.token_ttl,
algorithm: 'RS256'
});
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: process.env.token_ttl,
expires: new Date(Date.now() + process.env.token_ttl),
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
const tokenCookie = await serialize('token', token, cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
Then in logout:
// grab from req.cookies.token and validate
const token = await extractToken(req);
// you can take action if it's invalid, but not really important
if(!token) {
...
}
// this is how we expire it - the options here must match the options you created with!
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: 0,
expires: 0,
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
// set to empty
const tokenCookie = await serialize('token', '', cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
Upvotes: 3
Reputation: 53
As you have used JWT the backend will always check 2 things 1. Proper token 2. If time is over for that particular (You should handle this one)
For 2nd point, if the user time is up than from frontend you may delete the token if you have stored the token in localstorage.
For logout when user clicks on logout just delete jwt from localstorage and redirect to login or other page
Upvotes: 0