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);
}
});
Related
Some minor information about the problem:
The problem does not occur when ran on localhost/develpoment.
Hosted on App engine with PostgresSQl and I can see that it adds session to Postgres Table in GCP.
I have a very weird problem regarding session. I am trying to implement a login for my web app using Steam-auth on the API. However, it only works to sign in when using Google chrome(not incognito mode). I have tried firefox and safari to but it wont work. The reason is that incognito mode and firefox sends different cookies or less cookies on every request.
I at first thought the problem might have been caused by no session store but after implementing connect-pg-simple it didn't fix it. So from what I can tell it must be a setting issue with the session.
I am sending all requests from with 'withCredentials:true'.
import axios from 'axios';
// config
// ----------------------------------------------------------------------
const HOST_API = process.env.NODE_ENV === "production" ? "https://www.norskins-api.com/api/v1/" : "http://localhost:3005/api/v1/";
const axiosInstance = axios.create({
baseURL: HOST_API,
withCredentials: true
});
axiosInstance.interceptors.response.use(
(response) => response,
(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong')
);
export default axiosInstance;
The site is hosted at norskins.com if you wanna inspect network and see the changes in the cookies.
Server.js:
//Over this are just a lot of imports
app.use(cors(corsOptions));
//SESSION SETUP TOOLS
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
//DDOS PROTECTION
app.use(compression())
app.use(helmet())
app.use(limiter);
//SESSION SETTINGS
app.set('trust proxy', true);
app.use(
session({
...sessionSettings, store: new (require('connect-pg-simple')(session))({
pool: pool
}),
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initiate Strategy
passport.use(
new SteamStrategy(
{
returnURL: BACKEND_URL + "/api/auth/steam/return",
realm: BACKEND_URL,
apiKey: "A SECRET", //this is obv correct
},
function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
)
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/", (req, res) => {
res.send("Welcome to the most clever backend of all time");
});
app.get("/api/v1/user", (req, res) => {
console.log(req.session.steamuser)
if (req.session.steamuser) {
res.send(req.session.steamuser)
}
else {
res.send(false)
}
});
app.get(
"/api/v1/auth/steam",
passport.authenticate("steam", { failureRedirect: "/" }),
function (req, res) {
res.send(req.user);
}
);
app.get(
"/api/auth/steam/return",
passport.authenticate("steam", { failureRedirect: "/" }),
function (req, res) {
logon(req.user);
req.session.steamuser = req.user;
res.redirect(FRONTEND_URL);
}
);
app.post("/api/v1/logout", (req, res) => {
req.session.destroy();
res.status(200).send();
});
app.listen(port, () => {
console.log("Listening, port " + port);
});
Session Settings:
const rateLimit = require('express-rate-limit');
const isProduction = process.env.NODE_ENV === 'production';
const sessionSettings = {
secret: "ThisSuperSecretKeyThatStackWontSee", //obv something else
saveUninitialized: true,
resave: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, httpOnly: true, secure: isProduction, sameSite: isProduction ? "none" : "lax"
},
name: 'Session_Id',
};
const urlSettings = {
FRONTEND_URL: isProduction ? "https://www.norskins.no" : "http://localhost:3000",
BACKEND_URL: isProduction ? "https://www.norskins-api.com" : "http://localhost:3005"
}
const corsOptions = {
origin: [urlSettings.FRONTEND_URL],
credentials: true, //access-control-allow-credentials:true
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD']
};
const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: isProduction ? 1000 : 50000, // 5 requests,
});
I have never hosted something on GCP before, but I have no clue why it only works to sing into chrome normal. and why everything else have different session_id on each request.
logon()
async function logon(user) {
const users = await db.query("SELECT * FROM users WHERE id=$1", [user.id]);
if (users.rows.length > 0) {
return;
}
else {
const dateToday = new Date().toISOString().substring(0, 10);
await db.query("INSERT INTO users(id,steam_name,last_updated) values($1,$2,$3) returning *", [user.id, user.displayName, dateToday]);
return;
}
}
This is a project with stack MEAN.
I use passport for authentication, but it doesn't work well.
After logging in, reloading the page, access is immediately requested, not recognizing that the user is authenticated.
This is my code (service, model js, component angular, app.js)
// app.component.ts
constructor(private _modalService: NgbModal, private _user:UserService, private _router:Router) {
this._user.userLogged().subscribe(
data => console.log(data),
error => this._router.navigate(['/login'])
)
}
// user.service.ts
login(body:any) {
return this._http.post('http://127.0.0.1:3000/users/login', body, {
observe: 'body',
withCredentials: true,
headers: new HttpHeaders().append('Content-type', 'application/json')
});
}
userLogged() {
return this._http.get('http://127.0.0.1:3000/users/user-logged', {
observe: 'body',
withCredentials: true,
headers: new HttpHeaders().append('Content-type', 'application/json')
});
}
// users.js
router.post('/login', function (req, res, next) {
passport.authenticate('local',function (err, user, info) {
if (err) return res.status(501).json(err);
if (!user) return res.status(501).json(info);
req.logIn(user, function (err) {
if (err) return res.status(501).json(err);
return res.status(200).json({message: "login ok"});
});
})(req, res, next);
});
router.get('/user-logged', isValidUser, function (req,res,next) {
return res.status(200).json(req.user);
});
function isValidUser(req,res,next) {
if (req.isAuthenticated()) return next();
return res.status(401).json({message: 'Non autorizzato'});
}
// app.js
var passport = require('passport');
var session = require('express-session');
const MongoStore = require('connect-mongo');
app.use(session({
name: 'myname.sid',
resave: false,
saveUninitialized: false,
secret: 'secret',
cookie: {
maxAge: 36000000,
httpOnly: false,
secure: false,
},
store: MongoStore.create({mongoUrl: 'mongodb://localhost/iHospital'})
}));
require('./passport-config');
app.use(passport.initialize());
app.use(passport.session());
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');
I'm starting with Nuxt JS trying to migrate an old site with passport local and express-session, I get to make the authentication based on this repository but the main problem comes when I reload the page the users logout, its appears that Nuxt JS it's not saving my user session on the browser. I mostly sure there is something that im forgetting to implement or im not understanding. How could I save my res session token on my user browser?. Btw I have my API running on a separate port, so im not sure if there is any problem on saving session from other port. Here is my code:
Login.vue
<script>
import axios from 'axios';
export default {
data() {
return {
error : false,
form : {
username: '',
password: ''
}
}
},
created() {
if(this.$store.state.user) {
return this.$router.push('/');
}
},
methods: {
async login () {
await this.$store.dispatch('login', {
username : this.form.username,
password: this.form.password
});
this.form.password = '';
this.form.username = '';
}
},
}
</script>
Store/Index.js:
import axios from "axios";
export const state = () => ({
user: null,
});
export const mutations = {
SET_USER(state, user) {
state.user = user;
},
};
export const actions = {
nuxtServerInit({ commit }, { req }) {
if (req) {
if (
typeof req.session !== "undefined" &&
typeof req.user !== "undefined"
) {
commit("SET_USER", req.user);
}
}
},
login({ commit }, { username, password }) {
return axios({
method: "post",
url: this.$axios.defaults.baseURL + "/auth/login",
credentials: "same-origin",
data: {
username,
password
}
})
.then(res => {
if (res.data.meta.error === true) {
throw res.data;
}
return res.data.user;
})
.then(authUser => {
commit("SET_USER", authUser);
});
},
};
API Index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const cors = require('cors') // Srsly. Fuck Cors
const morgan = require('morgan');
const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);
const passport = require('passport');
const { database } = require('./config/keys');
const config = require('./config/config.json');
require('./lib/bAuth');
app.disable('view cache');
app.disable('x-powered-by');
app.set('port', process.env.PORT || config.port);
app.use(helmet());
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(session({
name: '_hsid',
key: 'sessionKey',
secret: config.session_secret,
resave: false,
saveUninitialized: false,
store: new MySQLStore(database),
cookie : {
maxAge: 1000 * 60 * 60 *24 * 365,
},
})
);
app.use(passport.initialize());
app.use(passport.session());
app.listen(app.get('port'), () => console.log(`[✔] Website connected and running at port: ${app.get('port')}`))
Login Route:
app.post('/login', async (req,res,next) => {
req.query = req.body;
auth.authenticate('user-login', (err,user,info) => {
if(user){
req.logIn(user,(err)=>{
if(err){
return res.json({
meta: {
error: true,
msg: err
}
});
}else{
if(req.isAuthenticated()){
if(!req.user.authenticated){
return res.json({
meta: {
error: true,
msg: "Bad credentials"
}
});
}else{
return res.json({
meta: {
error: false
},
user: bAccess.cleanUser(Object.assign({}, user))
});
};
}
}
});
}else{
return res.json({
meta: {
error: true,
msg: "Bad credentials"
}
});
}
})(req,res,next);
});
Passport Config:
auth.use('user-login', new Strategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true
}, async (req, username, password, done) => {
const _user = await _context.query('select username,password from users where username = ?', username);
if (!_user[0]) return done(null,false);
if (!_user[0].password || !await bAccess.verifyPassword(password, _user[0].password)) {
return done(null, false);
}
done(null, _user[0], {scope : '*'});
}, ));
auth.serializeUser(function (user, done) {
done(null, user);
});
auth.deserializeUser(async (user, done) => {
done(null, user);
});
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
});