Passport.js local strategy gives 401 error when trying to authenticate - mongodb

The authentication process gives 401 bad request. Though the new user is saved to db with hashed password I noticed is saved to MongoDB Atlas. I'm using the local strategy. If I try "local-signup" for the register t I reach the secret route, but for the login route I still get the 401 bad request.
Or can there be an authenticaiton issue with mongodb atlas trying to access the credentials??
app.post("/register", function (req, res, next) {
var newUser = new User({
username: req.body.username
});
User.register(newUser, req.body.password, function (err, user, info) {
console.log(user);
if (err) {
return res.render("register");
} else {
// go to the next middleware
next();
}
res.status(401).send(info);
});
}, passport.authenticate('local', {
successRedirect: '/secret',
failureRedirect: '/login'
}));
app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/secret",
failureRedirect: "/login"
}),
function (request, response) {}
);

Found the error. my misstake
passport.use(new localStrategy(User.authenticate));
shold be
passport.use(new localStrategy(User.authenticate()));

Related

nextauth v4 credentials provider, adding the raw token to the session

I am new to nextauth credentials provider, and I have been following different tutorials on youtube and searching for answers here.
I have a web application using next.js and in it I have a bunch of rest apis to get data from mongodb. I have secured the api by accessing the token. I have used Postman to test the apis, and they work when I pass the raw token to in the Authorization header.
I need to get the raw token into the session object for the session call back in next-auth, so I then can call the apis from client side pages.
Any help would be appreciated.
In [...nextauth].js:
export default NextAuth({
providers: [
// Google Provider
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET
}),
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}),
CredentialsProvider({
id: "credentials",
name: "Credentials",
async authorize(credentials, req) {
console.log("In Authorization");
connectMongo().catch((error) => {
error: "Connection Failed...!";
});
// check user existance
const user = await Users.findOne({ email: credentials.email });
if (!user) {
throw new Error("No user found with this email");
}
// compare()
const checkPassword = await compare(
credentials.password,
user.password
);
// incorrect password
if (!checkPassword || user.email !== credentials.email) {
throw new Error("Email or Password don't match");
}
// check if user is enabled
if (user.active === AccountStatus.DISABLED) {
throw new Error(
"Account has been disabled. Please contact support to re-enable your account"
);
}
// Value returned will go into token property
//console.log("Returnng User Object", user);
return user;
}
})
],
session: {
strategy: "jwt",
maxAge: 60 * 60 * 24
},
callbacks: {
async jwt({ token, user, account, profile, isNewUser }) {
if (user) token.user = user;
if (account) token.accessToken = account.access_token;
return token;
},
async session({ session, token, user, account }) {
// Send properties to the client, like an access_token from a provider.
const { password, ...tokenPwdRemoved } = token.user;
session.user = tokenPwdRemoved;
return session;
}
},
pages: {
signIn: "/login"
}
});
Take a look at the Session callback:
callbacks: {
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken
return session
}
}
Keep in mind security concerns relating to the token and session.
Session callback
The session callback is called whenever a session is checked. By
default, only a subset of the token is returned for increased
security. If you want to make something available you added to the
token through the jwt() callback, you have to explicitly forward it
here to make it available to the client.

finding the user while assigning new access token

I have a website where when user logsIn, they are assigned an access and a refresh token. When the access token is expried, a request to the server is made and checks if the refresh token is present in the global array in the database. If it is, a new access token is assigned to the user.
But I wanted to ask if should also check for the user by the information given by the refresh token when it is decoded. Or it is not necessary.
Please suggest me good practice and also tell me if something is wrong with my process.
routes.post("/newAccessToken", async (req, res) => {
const token = req.headers.cookie?.split("=")[1];
try {
const existingToken = await refreshTokens.findOne({
tokens: { $in: [token] },
});
if (existingToken) {
const email = await jwt.verify(token, process.env.RefreshTokenSecret);
if (email) {
const user = await userSchema.findOne({ email });
if (user) {
const newAccessToken = await jwt.sign(
{ email },
process.env.AccessTokenSecret
);
res.json({ newAccessToken });
}
} else res.json({ message: "token is invalid" });
} else res.json({ message: "No token found" });
} catch (error) {
console.log(error);
}
});

How to log out using jwt token in node backend

I have used jwt token to login
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
.
Below is my code for router
router.post("/login", async (req, res) => {
try {
const { email, password } = req.body;
// validate
if (!email || !password)
return res.status(400).json({ msg: "Not all fields have been entered." });
const user = await Customer.findOne({ email: email });
if (!user)
return res
.status(400)
.json({ msg: "No account with this email has been registered." });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ msg: "Invalid credentials." });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
res.json({
token,
user: {
id: user._id,
displayName: user.displayName,
},
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
Can anybody provide code for loging out using jwt token
You just need to invalidate your jwt token in logout
more than one way you can achieve. Here I am going to explain a couple of ways
1.storing token in an array. In log out, you can remove the token
const refreshTokens = []; --> global declaration
in login, before res.json({...});
refreshTokens.push(refreshToken);
the constraint here is jwt tokens are time-bounded. You need to get a refresh token if old token expires. Whenever you issue a refresh token you need to remove the old and push the latest
router.post('/refreshtoken', function (req, res) {
const { token } = req.body;
if (!token) {
return res.sendStatus(401);
}
if (!refreshTokens.includes(token)) {
return res.sendStatus(403);
}
jwt.verify(token, refreshTokenSecret, (err, user) => {
if (err) {
return res.sendStatus(403);
}
const accessToken = jwt.sign({ username: user.username, role: user.role }, accessTokenSecret, { expiresIn: '20m' });
refreshTokens = refreshTokens.filter(token => t !== token);
refreshTokens.push(accessToken);
res.json({
accessToken
});
});
});
In Logout you need to invalidate token
app.post('/logout', (req, res) => {
const { token } = req.body;
refreshTokens = refreshTokens.filter(token => t !== token);
res.send("Logout successful");
});
2.Store token in cookie whenever you log in or reissues the token. verify jwt token from cookie instead of reading from headers.
res.cookie('jwt_token', token, {
expires: new Date(Date.now() + expiration),
secure: false, // set to true if your using https
httpOnly: true,
});
In Logout destroy the cookie
router.get('/logout', function (req, res) {
res.clearCookie('jwt_token');
req.session.destroy();
});

Mongoose not fetching data until I refresh the database connection

I am trying to re-fetch the data from MongoDB using mongoose whenever a user reloads the page. However, the old data stays there and the new data doesn't get fetched until I restart the server.
Here is the router:
router.post("/dashboard", (req, res) => {
const userId = req.body.userId;
User.findOne({ _id: userId }, (err, users) => {
if (err) {
console.log(err);
res.status(500).send();
} else {
router.get("/dashboard", (req, res, next) => {
const leagues = [users.leagues.premium, users.leagues.free];
if (err) return next(err);
res.status(200).send(leagues);
});
}
});
});
And here is the Actions (Redux):
export const fetchLeagues = userId => dispatch => {
axios.post("/api/leagues/dashboard", userId).then(
setTimeout(function() {
axios.get("/api/leagues/dashboard").then(leagues => {
dispatch({
type: GET_LEAGUES,
payload: leagues
});
});
}, 50)
);
};
The data must be fetched from a specific user, so that's why I am posting the user Id, then getting the data back. Not sure if this is the best way of doing this.
Just to clarify, I am using the MERN stack with redux and axios to execute this. I tried to use this: MongoDB does not refresh data automatically?, but I still can't get this thing to refresh/re-fetch the data when the router is called again. Thanks.
Doing a POST request then a GET request seems unnecessary here as you can just return the data in a single request.
The reason why the data is being persisted is because when you declare the router.get('/dashboard') route you are permanently hardcoding that route to have the values from the first request.
It's probably best to use a GET request, as that is what you are trying to do.
e.g.
router.get("/dashboard/:userId", (req, res) => {
const userId = req.params.userId;
User.findOne({ _id: userId }, (err, users) => {
if (err) {
console.log(err);
res.status(500).send();
} else {
const leagues = [users.leagues.premium, users.leagues.free];
if (err) return next(err);
res.status(200).send(leagues);
}
});
});
// Where userId is now a string
export const fetchLeagues = userId => dispatch => {
axios.get(`/api/leagues/dashboard/${userId}`).then(leagues => {
dispatch({
type: GET_LEAGUES,
payload: leagues
});
});
};

JWT - Why it generates extraordinary long tokens

Everything was working fine until I decided to save the tokens (as String) with the users. The tokens were used to be 5 lines long max, and now it is keep growing every time I refresh the token. The last token I generated was 100 lines long which is not acceptable.
Every time the user logs in, I am refreshing the token.
module.exports.login = function(req, res){
var user_name = req.body.username;
var password = req.body.password;
User.findOne({username: user_name}, function(err, user){
if(err || !user) {
return res.json({error: "cannot find the user"});
} else{
user.comparePassword(password, function(err, isMatch){
if (err){
return res.json({
error: "passowrd doesn't match"
});
}
});
var token = jwt.sign(user, process.env.SECRET, {
expiresIn: 4000
});
console.log(token); // printing the token
}
if(!token){
res.json({
success: false,
username: null,
token: null
});
}
else {
user.token = token;
User.updateUser(user._id, user, {new: true},function(err, updated_user){
res.json({
success: true,
username: user.username,
token: token
});
});
}
});
};
All the routes are secured, and it needs to verify the token for each request.
module.exports.secured = function(req, res, next){
var token;
var username = req.body.req_username || req.headers['req_username'];
if(username){
User.findOne({ 'username': username }, function (err, user) {
if (err || !user)
return res.json({
error: "cannot find the user"
});
else
token = user.token;
jwt.verify(token, process.env.SECRET, function(err, decode){
if(err){
res.status(500).send({
error: "wrong token or username"
});
} else{
next();
}
});
});
} else{
res.send({
error: "not found"
});
}
};
I think I am not refreshing the tokens correctly.
The token should not be stored in the server because it wastes unnecessary resources.
user.token = token;
User.updateUser(user._id, user, {new: true},function(err, updated_user){
res.json({
success: true,
username: user.username,
token: token
});
});
Each time the token is refreshed you encode the user variable that includes the previously issued token, therefore it grows
var token = jwt.sign(user, process.env.SECRET, {
expiresIn: 4000
});
Remove user.token = token
But the main issue is that the client MUST send the JWT in each request, not the username to recover the token. You are verifying the token stored in user entity. It does not make sense. Change the client code to.send the JWT in the headers instead of the username req.headers['req_username']