How to send express-session to client not in same url? - node.js

Can I please get some help, I have been searching all over the internet for this and I cannot seem to find the answer that I am looking for.
So I would love to implement expression-session and mongoDB as the store, so session creation I managed to get that done and also saving the session within the store i.e MongoDB managed to get that done.
The problem now comes to when I have to send the session cookie to the client, so I have my server(NodeJS) running on port 5000 and I have the client(ReactJS) running on port 3000, so now how can I send that cookie to the client and flag it as httpOnly cookie?
This is how I tried to setup express-session
mongoose
.connect(MongoURI, {
useNewUrlParser: true,
useCreateIndex: true
})
.then(() => console.log("MongoDB connected..."))
.catch((err) => console.log(err));
const mongoDBstore = new MongoDBStore({
uri: MongoURI,
collection: "mySessions"
});
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(
session({
name: COOKIE_NAME,
secret: 'SESS_SECRET',
resave: true,
saveUninitialized: false,
store: mongoDBstore,
cookie: {
maxAge: 1000 * 60 * 60 * 3,
sameSite: false,
secure: false
}
})
);
This is where I wanted to send the cookie once user logs in
router.post("/login", (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ msg: "Please enter all fields" });
}
User.findOne({ email }).then((user) => {
if (!user) return res.status(400).json({ msg: "User does not exist" });
bcrypt.compare(password, user.password).then((isMatch) => {
if (!isMatch) return res.status(400).json({ msg: "Invalid credentials" });
const sessUser = { id: user.id, name: user.name, email: user.email };
req.session.user = sessUser;
res.json({ msg: " Logged In Successfully", sessUser });
});
});
});

Related

Cookie not set, even though it is in response headers. Using express-session cookies

Problem:
Trying to set the cookie on login using express-session, but think I'm missing something obvious. The response to the login POST request includes Set-Cookie. I've also set the Access-Control-Allow-Origin and Access-Control-Allow-Headers to wildcards as shown here:
https://i.stack.imgur.com/XS0Zv.png
But we see that in the browser storage (tried with Firefox and Chrome) there is nothing. As shown here
I'm currently setting my express-session as follows (refer to end of post for full code. Adding snippet for easier read):
app.use(session({
genid: () => { return uuidv4(); },
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
sameSite: true,
}
})
);
Then after I've verified the user is getting logged in, I try to set the userId via:
req.session.userId = user.id;
Possibly Relevant Info
These sessions are successfully getting stored in Mongo as you can see here, which makes me believe that I'm at least generating the sessions correctly. Now I could be totally wrong here...
my backend is running on localhost:8000 via: app.listen(8000);
my client is running on http://localhost:3000/
trying not to use Apollo GraphQL for learning purposes
Things I've tried so far:
different combinatons of resave, saveUnitialized.
remove the cookie parameter.
stop setting userId
restarting browser and servers
Looked at relevant stack overflow posts
Please advise! Even ideas on how to debug this or what other things I can look at would be immensely helpful!
Relevant Code
app.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const {v4: uuidv4} = require('uuid');
const graphqlSchema = require('./graphql/schema/index');
const graphqlResolvers = require('./graphql/resolvers/index');
const app = express();
const path = '/graphql';
app.use(bodyParser.json());
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
mongoose
.connect(`mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}#cluster0.ccz92.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
{ useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }
)
.then(() => {
app.use(session({
genid: () => { return uuidv4(); },
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
sameSite: true,
}
})
);
app.use(path, graphqlHTTP({
schema: graphqlSchema,
rootValue: graphqlResolvers,
graphiql: true,
}));
app.listen(8000);
})
.catch(err => {
console.log(err);
});
graphql/resolvers/auth.js
const argon2 = require('argon2');
const jwt = require('jsonwebtoken');
const User = require('../../models/user');
module.exports = {
createUser: async args => {
try {
const existingUser = await User.findOne({
email: args.userInput.email
});
if (existingUser) {
throw new Error('User exists already.');
}
const hashedPassword = await argon2.hash(
args.userInput.password,
12
);
const user = new User({
email: args.userInput.email,
password: hashedPassword,
loggedIn: true
});
const result = await user.save();
const token = jwt.sign(
{ userId: result.id, email: result.email },
process.env.JWT_KEY,
{ expiresIn: '1h' }
);
return {
userId: result.id,
token: token,
tokenExpiration: 1
};
} catch (err) {
console.log("error in resolvers/auth.js");
throw err;
}
},
login: async (args, req) => {
const { userId } = req.session;
if (userId) {
console.log("found req.session");
return User.findOne({ _id: userId });
}
console.log("looking for user with ", args.userInput.email);
const user = await User.findOne({ email: args.userInput.email });
console.log("found user");
if (!user) {
throw new Error("User does not exist!");
}
user.loggedIn = true;
user.save();
const isEqual = await argon2.verify(user.password, args.userInput.password);
if (!isEqual) {
throw new Error ("Password is incorrect!");
}
console.log("setting session.userId");
req.session.userId = user.id;
return { ...user._doc, password: null};
},
logout: async (args, req) => {
if (!req.isAuth) {
throw new Error('Unauthenticated');
}
try {
const result = await User.findOneAndUpdate(
{ _id: req.userId },
{ loggedIn: false },
{ new: true },
);
return { ...result._doc, password: null };
} catch (err) {
console.log("logout error", err);
throw(err);
}
},
};
So it turned out to be a CORS issue. I didn't realize that the port would mean a different origin. In this case my client is at 3000 and my server is at 8000.
Given the CORS nature, in the client I need to include credentials (cookies, authorization headers, or TLS client certificates) when I'm fetching:
fetch(config.url.API_URL, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json'
},
credentials: "include",
})
This will tell the user agent to always send cookies.
Then serverside I need to set Access-Control-Allow-Credentials to be true as such:
res.setHeader('Access-Control-Allow-Credentials', true);
This will allow the browser to expose the response (which has the cookie) to the frontend Javascript code.
Since we are using credentials, we will need to specify Access-Control-Allow-Headers and Access-Control-Allow-Origin
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');

How can I use session variables across whole app

I'd like to access session variables assigned just after login further in mysql queries on different path. However when I do that what I receive is "undefined" value. Here's my login script.
users.post('/login', (req, res) => {
User.findOne({
where: {
email: req.body.email
}
})
.then(user => {
if (user) {
if (bcrypt.compareSync(req.body.password, user.password)) {
let token = jwt.sign(user.dataValues, process.env.SECRET_KEY, {
expiresIn: 1440
})
req.session.Osoba=user.Osoba
res.send(token)
}
} else {
res.status(400).json({ error: 'Taki użytkownik nie istnieje' })
}
})
.catch(err => {
res.status(400).json({ error: err })
})
})
Here's session middleware (it's actually at the beggining of the main node file, just after requiring all packages and before any routes):
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
Here's the line which gives me undefined.
app.get('/wypozyczanie', async (req, res) => {
if (req.session) {console.log(req.session.Osoba)}
})
Try your session middleware as bellow.
app.use(session({
key: 'sess_key',
secret: 'secret',
saveUninitialized: true,
resave: true,
rolling: true,
cookie: {
expires: 3600000, //1 hour. Sewt as you want
secure: false
}
}));
Make sure req.session.Osoba is assigned before you are using it.

Session not saving after login

I have a problem with express-session. When I try to login I have to save my userdata in cookies and after redirect get it on home page. Now after redirect my cookie is clearing and I have default cookie without some data
const authRoute = require('./routes/auth')
mongoose.connect(
process.env.DB_CONNECT,
{ useUnifiedTopology: true, useNewUrlParser: true },
() => {
console.log('Connected to DB')
});
//Middleware
app.use(express.json())
app.use(cors())
app.use(session({
name: 'sid',
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESS_SECRET,
cookie: {
maxAge: 1000 * 60 * 60 * 2,
sameSite: true,
secure: false
}
}))
app.use('/api/user', authRoute)
Routes file
router.get('/login', (req, res) => {
console.log(req.session);
if (req.session.user) {
res.status(200).send(req.session.user)
} else res.status(403).send({ error: 'You need to login first' })
})
router.post('/login', async (req, res) => {
...
req.session.user = { id: user._id, username: user.name, email: user.email }
req.session.save()
//CREATE AND ASSIGN TOKER
const token = jsw.sign({ _id: user._id }, process.env.TOKEN_SECRET)
res.header('auth-toker', token).send(user)
})
Try disabling the Windows Defender or other anti virus software. Those may not allow the connection to go through

Session data are lost using express session

I'm working in devMode with angularjs and express-session with cors middleware and I run frontend from localhost:4200 and backend from localhost:8080
In login request I set user data in session and then when I call "/api/contacts", the session user data is undefined.
I tried to save session with session.save() but it does not work.
I noticed that between calls sessionID changes.
I searched for hours on google but I have not found any solution.
this is the frontend call to "/api/contacts"
this.http.get(environment.apiUrl + '/api/contacts', {
withCredentials: true,
})
this is part of server.js
app.use(cors({origin: [
"http://localhost:4200"
], credentials: true,
}));
let sess = session({
secret: 'my secret',
resave: false,
saveUninitialized: false,
store: new MemoryStore({
checkPeriod: 60000 * 5 // prune expired entries every 24h
}),
cookie: {
secure: app.get('env') === 'production'?true:false,
maxAge: 60000 * 5 ,
}
})
app.use(sess)
// Initialize the app.
var server = app.listen(process.env.PORT || 8080, function () {
});
const authMiddleware = (req, res, next) => {
// here req.session.user IS undefined
if(req.session && req.session.user) {
next();
} else {
res.status(403).send({
status: 403,
errorMessage: 'You must be logged in.'
});
}
};
app.get("/api/contacts", authMiddleware,(req, res) => {
// some code will run if authMiddleware pass
});
app.post('/api/login', validatePayloadMiddleware, (req, res) => {
if (req.body.username === "xx.xxxx#xxxx.xxx" && req.body.password === "xxxxxxx")
{
let user = {
id: req.sessionID,
username: req.body.username,
firstName: "Fabio",
lastName: "Spadaro",
};
req.session.user = user;
req.session.save((err) => {
console.log(err)
});
return res.status(200).json(user);
}
else
{
let body = {
error: true,
errorMessage: 'Permission denied!'
};
return res.status(403).json(body);
}
});

node.js - Passport not persisting across browser requests, works with Postman

I am currently using the create-react-app boiler plate and have been attempting to add auth. I am using axios as my promise based HTTP libray with React.js. I have been using node with express, express-session, passport and passport-local on the backend.
Here is my server.js file with some exlusions:
const express = require('express');
const mysql = require('mysql');
const app = express();
const cors = require('cors');
const session = require('express-session');
const passport = require('passport');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const LocalStrategy = require('passport-local').Strategy;
// Express only serves static assets in production
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
}
app.set('port', (process.env.PORT || 3001));
app.use(cors({
credentials: true,
origin: 'http://localhost:3000'
}));
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(session({
secret: 'topsecretpassword',
resave: true,
saveUninitialized: false,
cookie: {
path: '/',
originalMaxAge: 1000 * 60 * 60 * 24,
httpOnly: true,
secure: false
}
}));
app.use(passport.initialize());
app.use(passport.session());
// Setup Database connection
const connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'mvy_db'
});
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(user, done) {
connection.query('SELECT * FROM users WHERE id=?', user, function(err, userId) {
if (err) {
res.status(400).json({
error: 'Database Error',
id: userId[0]
});
}
done(err, userId[0]);
});
});
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
},
function(email, password, done) {
connection.query('SELECT * FROM users WHERE email=?', email, function(err, user) {
if (err) {
return done(err);
}
if (!user.length) {
return done(null, false, { message: 'Incorrect email.' });
}
if (user[0].password !== password) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user[0]);
});
}
));
app.post('/signin', passport.authenticate('local'), function(req, res) {
console.log(req.session);
return res.send('login success!');
});
function isAuthenticated (req,res,next){
console.log(req.session);
if(req.session.passport.user)
return next();
else
return res.status(401).json({
error: 'User not authenticated'
})
}
app.get('/checkauth', isAuthenticated, function(req,res) {
res.status(200).json({
status: 'User Authenticated!'
});
})
app.get('/signout', function(req,res) {
req.session.destroy();
res.status(200).json({ success: 'successfully signed out' });
})
Using postman (and even on the browser), I am able to successfully login and the following is held in the req.session object :
cookie:
{ path: '/',
_expires: null,
originalMaxAge: 86400000,
httpOnly: true,
secure: false },
passport: { user: 1 } }
my login request using axios:
return axios.post(ROOT_URL + 'signin', {
email: e.target.email.value,
password: e.target.password.value
}).then((response) => {
if (response.status === 200) {
console.log(response);
}
})
My checkAuth request using axios (this is where I get a 500 error returned):
axios.get(ROOT_URL + 'checkauth', { withCredentials: true })
.then((response) => {
if (response.status === 200) {
return true;
} else {
return false;
}
});
The req.session object after checking authentication before the error message, note that the passport object doesn't exist anymore:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: 86400000,
httpOnly: true,
secure: false } }
This is the error message I get on the console when I attempt to check that the user is authorized:
TypeError: Cannot read property 'user' of undefined
at isAuthenticated (/server.js:94:26)
I've been banging my head for hours, trying to resolve this issue. I thought it might have something to do with CORS, but after hours of playing around with it that doesn't seem to be the case. It's still plausible that it's a CORS issue, but what's really flustering me is that it works full well with Postman but not on my Chrome browser. Any help is appreciated!
Alright, so I found the solution to my problem. It appeared to be an issue with axios and the configuration of my get requests. For some reason, using the structure axios.get(URL) .then(response) doesn't work with the withCredentials property.
Instead, I had to send my request as:
axios(ROOT_URL + 'checkauth', {
method: 'get',
withCredentials: true
})
.then((response) => {
if (response.status === 200) {
return true;
} else {
return false;
}
});
Oh because I forgot that axious doesn’t send credentials by default I had to stick with jwt and completely removed session.
You can define an instance of axious which will allow you to make requests much more simply
const $axios = axios.create({
baseURL: 'https://some-domain.com/api/',
withCredentials: true
});

Resources