Problem with logout user. Express server api with JWT auth - express

I'm trying to create a full-stack mern app. I use jwt authentication. I will save the token in local storage and the token will be removed from there with react.
I have tried with req.logout(), req.logOut() and req.session.destroy() and it doesn't work.
Thank you!
const signIn = (req, res) => {
const { email, password } = req.body;
User.findOne({ email: email })
.then((user) => {
if (!user) {
const error = new Error('A user with this email could not be found');
error.statusCode = 401;
throw error;
}
if(!user.authenticate(password)) {
const error = new Error('A user with this email could not be found');
error.statusCode = 401;
throw error;
}
const token = jwt.sign({
email: user.email,
userId: user._id.toString()
}
, 'somesupersecret'
, { expiresIn: '1h' });
res.status(200).json(
{
message: 'User successfully logged in!',
token,
userId: user._id.toString()
});
})
.catch(error => {
if (!error.statusCode) {
error.statusCode = 500;
next(error);
}
})
}
const logOut = async(req, res) => {
try{
await req.logOut();
res.status(200).json(
{
message: 'User successfully logged out!',
});
}
catch(error) {
console.log(error);
}
}
module.exports = {
signUp,
signIn,
logOut
}
I become errors like TypeError: req.logOut is not a function.

This should not be done on the server. Just remove/invalidate your jwt token that is being sent by the client. Generally the token is stored in localStorage and/or cookies. Just delete that token and refresh the page, user will logout.

Related

Getting error in MERN stack Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

I am new to MERN. I was using this code below . I followed a youtube tutorial and it was working fine for me for 4 to 5 days but now suddenly it has stopped working. I didn't change anything. I am not able to login, logout or even fetch data. My postman is giving positive results using these api but it won't work on my code. I want to remind you guys again, it was working fine for 4 to 5 days.
const User = require("../model/user");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const signup = async (req, res, next) => {
const { fname, lname, email, password, role, phone } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
console.log(err);
}
if (existingUser) {
return res.status(400).json({ message: "user already exists" });
}
const hashedPassword = bcrypt.hashSync(password);
const user = new User({
fname,
lname,
email,
password: hashedPassword,
phone,
role,
});
try {
await user.save();
} catch (err) {
console.log(err);
}
return res.status(201).json({ message: user });
};
const login = async (req, res, next) => {
const { email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
console.log(err);
}
if (!existingUser) {
return res
.status(400)
.json({ message: "user doesn't exist. Please signup" });
}
const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password);
if (!isPasswordCorrect) {
return res.status(401).json({ message: "invalid email or password" });
}
const token = jwt.sign({ id: existingUser._id }, "change1122", {
expiresIn: "1h",
});
res.cookie(String(existingUser._id), token, {
path: "/",
expires: new Date(Date.now() + 1000 * 3600),
httpOnly: true,
sameSite: "lax",
});
return res
.status(200)
.json({ message: "user logged in sucessfully", user: existingUser, token });
};
const verifyToken = (req, res, next) => {
const cookies = req.headers.cookie;
const token = cookies.split("=")[1];
if (!token) {
res.status(404).json({ message: "no token found" });
}
jwt.verify(String(token), "change1122", (err, user) => {
if (err) {
return res.status(404).json({ message: "invalid token" });
}
req.id = user.id;
});
next();
};
const getUser = async (req, res, next) => {
const id = req.id;
let user;
try {
user = await User.findById(id, "-password");
} catch (err) {
console.log(err);
}
if (!user) {
res.status(404).json({ message: "user not found with the id" });
}
return res.status(200).json({ user });
};
const logout = async (req, res, next) => {
const cookies = req.headers.cookie;
console.log(cookies);
const token = cookies.split("=")[1];
if (!token) {
res.status(404).json({ message: "no token found" });
}
const user = req.id;
res.clearCookie(`${user}`);
req.cookies[`${user}`] = "";
return res.status(200).json({ message: "successfully logged out" });
};
exports.signup = signup;
exports.login = login;
exports.verifyToken = verifyToken;
exports.getUser = getUser;
exports.logout = logout;
Here is the error
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5) at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (E:\Reacct\pos\server\node_modules\express\lib\response.js:794:10)
at ServerResponse.send (E:\Reacct\pos\server\node_modules\express\lib\response.js:174:12)
at ServerResponse.json (E:\Reacct\pos\server\node_modules\express\lib\response.js:278:15)
at getUser (E:\Reacct\pos\server\controller\user-controller.js:86:25)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
[nodemon] app crashed - waiting for file changes before starting...
I think i have an issue with cookies or token, I am new so i don't understand it properly.

Login authenticates user with any password

I have a simple login form that I created. It seems to authenticate any existing user as long as the password field has something in it. Obviously, it is a huge security flaw. I'm new to mean stack and using passport to authenticate users seemed easy but not sure if I did it wrong.
This is my backend code using passportjs:
app.js
const passport = require('passport');
require('./config/passport');
app.use(passport.initialize());
routes/index.js
const ctrlAuth = require('../controllers/authentication');
router.post('/login', ctrlAuth.login);
controllers/authentication.js
module.exports.login = function(req, res) {
passport.authenticate('local', function(err, user, info){
let token;
// If Passport throws/catches an error
if (err) {
res.status(404).json(err);
return;
}
// If a user is found
if(user){
token = user.generateJwt();
res.status(200);
res.json({
"token" : token
});
} else {
// If user is not found
res.status(401).json(info);
}
})(req, res);
};
And finally, my config file
config/passport.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const User = mongoose.model('User');
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(username, password, done) {
User.findOne({
email: username
}, function(err, user) {
if (err) {
return done(err);
}
//Return if user not found in database
if (!user) {
return done(null, false, {
message: 'User not found'
});
}
//Return if password is wrong
if (!user.validPassword(password)) {
return done(null, false, {
message: 'Password is wrong'
});
}
//If credentials are correct, return the user object
return done(null, user);
});
}
));
I believe I've narrowed the bug down to my validPassword function where I might be using bcrypt incorrectly.
userSchema.methods.validPassword = function(password){
return bcrypt.compare(password, this.hash);
};
I narrowed my issue down to my validPassword method and found that I was using bcrypt incorrectly. Changed it to
userSchema.methods.validPassword = function(password){
return bcrypt.compareSync(password, this.hash);
};
Makes more sense after looking at the docs for bcrypt https://github.com/kelektiv/node.bcrypt.js#readme

How do I handle passport js redirects from Nuxt SSR?

I am using Nuxt SSR with express session and I have a passport JS redirect from the server side
/**
* POST /signup
* Create a new local account.
*/
exports.postSignup = (req, res, next) => {
const validationErrors = [];
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: 'Password must be at least 8 characters long' });
if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: 'Passwords do not match' });
if (validationErrors.length) {
req.flash('errors', validationErrors);
return res.redirect('/signup');
}
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
const user = new User({
email: req.body.email,
password: req.body.password
});
User.findOne({ email: req.body.email }, (err, existingUser) => {
if (err) { return next(err); }
if (existingUser) {
req.flash('errors', { msg: 'Account with that email address already exists.' });
return res.redirect('/signup');
}
user.save((err) => {
if (err) { return next(err); }
req.logIn(user, (err) => {
if (err) {
return next(err);
}
res.redirect('/');
});
});
});
};
If I call the redirect method? it would reload the page and clear Vuex state right?
How do I do this redirect from passport such that Vuex state is kept intact and client page does not refresh
It is indeed better to asynchronously handle form submissions to avoid page refresh as #Darius mentioned. But for completion's sake I'd like to mention that solutions do exist to persist your Vuex state, such as vuex-persistedstate.
It can be used to persist the state to localStorage, sessionStorage, or even cookies. It can also be used as a Nuxt plugin.

OpenId issue for authentication

I have an embarassing issue with cognito.
My authentication strategy works with current usage but when I try to run tests that sign up a new user and then log it in for an access to other APIs in my website
const authenticationData = {
Username: req.body.email,
Password: req.body.password,
};
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
const poolData = {
UserPoolId: config.development.UserPoolId,
ClientId: config.development.ClientId,
TokenScopesArray : config.development.TokenScopesArray
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
const userData = {
Username: req.body.email,
Pool: userPool,
TokenScopesArray : config.development.TokenScopesArray
};
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('success')
token = result.getAccessToken().jwtToken;
const idToken = result.idToken.jwtToken;
console.log(token)
res.cookie("accessToken",token)
res.status(200).send(token);
},
onFailure: function (err) {
console.log(err)
res.status(404).send(err)
},`
Then when I try to authenticate with the following code :
app.use(function (req, res, next) {
var token = req.body.token || req.query.token || req.cookies.accessToken || req.headers['x-access-token'];
try {
if (token) {
let promise = new Promise((resolve, reject) => {
const data = null;
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log('response', this.responseText);
}
})
xhr.open("GET", "https://gridmanager.auth.us-east-1.amazoncognito.com/oauth2/userInfo");
xhr.setRequestHeader("Authorization", "Bearer " + token);
xhr.setRequestHeader("cache-control", "no-cache");
xhr.setRequestHeader("TokenScopesArray", config.development.TokenScopesArray)
xhr.send(data);
resolve(xhr.responseText)
})
.then(function (response) {
if (response != null) {
res.decoded = response
next();
}
else {
return res.status(404).send('User not authenticated')
}
})
}
else {
console.log('No token')
return res.status(403).send('No token')
}
} catch (error) {
// if there is no token
// return an error
console.log('error')
return res.status(403).send({
success: false,
message: error.message
});
}
I get the following error in xhr.responseText :
{"error":"invalid_token","error_description":"Access token does not contain openid scope"}
And when I log the accessToken I get in the login function, it only has 'aws.cognito.signin.user.admin'
I already tried to change the settings in my appclient but nothing works
Thanks for your help
Unfortunately, only access tokens issued by the Cognito hosted UI can include scopes other than aws.cognito.signin.user.admin. Cognito hosted UI supports OpenId Connect and Cognito API doesn't. It's a big gap in terms of functionality provided by those two. The /oauth2/userInfo endpoint is part of the Hosted UI and it also follows the OpenID Connect spec.
Why do you want to call the /oauth2/userInfo endpoint when you have access to the id_token? The id_token payload has all the information about the user that /oauth2/userInfo would return.

undefined jwt token react/express

I am implementing a JWT authentication on a login/registration system. When there is a successful login/registration I am setting a user token in localStorage.
Problem is when I check my localStorage the user key is present but the value is undefined. I think the issue might be in my axios post or in my express file but I can't quite figure it out.
// my action creator
export function login(user, history) {
return async (dispatch) => {
axios.post('/api/login', { user })
.then(res => {
dispatch({ type: AUTHENTICATED });
localStorage.setItem('user', res.data.token);
history.push('/');
})
.catch((error) => {
dispatch({
type: AUTHENTICATION_ERROR,
payload: 'Invalid email or password'
});
});
};
}
The data is reaching my api correctly. The item is being set but the value res.data.token is undefined.. Below is my express file
// login.js (/api/login)
router.post('/', function(req, res) {
var email = req.body.user.email;
var password = req.body.user.password;
// TODO: create db file and import connection
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
database: "dbname",
port: 3307
});
connection.connect(function(err) {
if(err) {
console.log(err);
} else {
connection.query("SELECT ID, Password FROM Users WHERE Email = ?", [email], function(err, result) {
if(err) {
console.log('Could not find account');
res.send(err);
} else {
var id = result[0].ID;
bcrypt.compare(password, result[0].Password, function(err, result) {
if(result) {
console.log(id);
res.json({ id });
} else {
console.log('Incorrect password');
}
});
}
});
}
});
});
Since the res.data.token in my action creator is returning undefined does that mean the response in my express file ( res.json([id]) ) is just returning defined?
You are not sending the response properly.
res.json([id]); Its just sending the array of id. That's why res.data.token is undefined. as data does not contain an object.
Send proper object like:
res.json({id});