I currently have a React App (via create-react-app) and an Express server for my API calls. They are both running separately and have been working fine until I ran into this problem:
I'm also using express-session and passport for user authentication. I'm able to authenticate users but the session doesn't persist between API calls. If I successfully login, the next time I make an API call, req.sessionID will be different, and req.isAuthenticated() will return false.
Here is my server code:
'use strict'
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var mongoose = require('mongoose');
var passport = require('passport');
var session = require('express-session');
var cors = require('cors');
var flash = require('connect-flash');
var store = require('./store');
var database = require('./database');
var app = express();
var port = process.env.port || 3001;
var router = express.Router();
var promise = mongoose.connect('mongodb://localhost:27017/lifeless-db', {useMongoClient: true});
//app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser('lifeless-secret-01890'));
app.use(session({
secret: 'lifeless-secret-01890',
saveUninitialized: true,
cookie: {
secure: false,
}
}));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
// Initialize Passport
var authinit = require('./auth/init');
authinit(passport);
// For debugging:
app.use(function(req, res, next) {
console.log("SessionID: " + req.sessionID);
console.log(req.isAuthenticated() ? "This user is logged in" : "This user is NOT logged in");
next();
});
// GET
app.get('/items', function(req, res) {
store.getAllItems((items) => {
if(!items) return res.send({error: true, message: 'Error loading store :/', items: null});
else return res.send({error: false, message: 'Success', items: items});
});
});
app.get('/login', function(req, res) {
console.log(req.flash());
console.log("Login fail");
res.send({error: true, message: 'Unknown error'});
});
// POST
app.post('/message', function(req, res) {
database.submitMessage(req.body.email, req.body.message, (success) => {
if (success) return res.send({error: false, message: 'Success'});
else return res.send({error: true, message: 'Error sending message'});
});
});
app.post('/login', passport.authenticate('local', {failureRedirect: '/login', failureFlash: true}), function(req, res) {
console.log(req.flash());
console.log("Login success");
return res.send({error: false, message: 'Success'});
});
// SERVER
app.listen(port, function(){
console.log('Server started.');
console.log('Listening on port ' + port);
});
And here is an example of an API call from the React App (using axios):
login(e) {
e.preventDefault();
axios({
method: 'post',
url: 'http://localhost:3001/login',
data: {
username: this.state.username,
password: this.state.password,
}
})
.then((response) => {
console.log(response.data);
if (response.data.error) {
this.setState({error: true, errmessage: response.data.message});
} else {
this.setState({redirect: true});
}
})
.catch((error) => {
this.setState({error: true, errmessage: 'Error logging in'});
});
}
I figure there must be some way to have React store the session somehow (?) but I'm fairly new to React and don't really know how to use it with an express backend.
Your axios request from your React client needs to be sent withCredentials. To fix it, either do axios.defaults.withCredentials = true; or do axios.get('url', {withCredentials: true})...Also in your expressjs backend, set cors options credentials: true
Here is an example of setting up express-session using connect-redis. First, setup both express-session and the Redis store.
var session = require('express-session);
var RedisStore = require('connect-redis')(session);
Then, where you're declaring middleware for your app;
app.use(session({
store: new RedisStore({
url: process.env.REDIS_URL
}),
secret: process.env.REDISSECRET,
resave: false,
saveUninitialized: false
}));
now when you use req.session.{etc} this writes to your Redis DB.
Related
I am trying to post a flash message after authenticating but for some reason the req.flash() loses its value after a redirect.
The code starts when the user asks for a GET on '/'. Here's the code:
const express = require('express');
const session = require('express-session');
const cookie = require('cookie');
const methodOverride = require('method-override')
const passport = require('passport');
const localStrategy = require ('passport-local').Strategy;
const bcrypt = require('bcryptjs');
const cookieParser = require('cookie-parser');
const flash = require('express-flash');
const fs = require('fs');
const config = require('./config.js');
const db = require('./db.js');
const log = require('./logger.js');
const crypto = require('crypto');
var app = express();
const server = require('http').createServer(app);
const build_version = fs.readFileSync("minorBuild.txt").toString();
process.on('UnhandledPromiseRejectionWarning', function(err) {
log.error({ section: "UnhandledPromiseRejectionWarningr", err: err, uid: '1', orgId: '1' });
});
app.set('port', 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(cookieParser(config.cookieSecret));
app.use(flash());
app.use(express.urlencoded({extended: false}));
app.use(express.json());
app.use(session({ name: 'sid',
secret: config.sessionSecret,
cookie: { maxAge: config.sessionCookieMaxAge, domain: 'localhost:3000' },
resave: true,
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static('public'));
app.use(methodOverride('_method'))
app.get('/', function(req, res, next) {
if (req.isAuthenticated()) {
res.redirect('/dashboard');
} else {
res.render('index', { messages: req.flash("error") });
})
}
});
Once the user POSTS username and password, the data is sent to this function:
app.post('/login', function(req, res, next){
passport.authenticate('local', function(err, user, info){
var redirectUrl = '/';
if (!user) {
req.flash('error', info.messages);
return res.redirect('/');
}
if (req.session.redirectUrl) {
redirectUrl = req.session.redirectUrl;
req.session.redirectUrl = null;
}
req.logIn(user, function(err){
if (err) {
return next(err);
}
});
res.redirect(redirectUrl);
})(req, res, next);
});
The data is authenticated here and the message is set on incorrect auth:
async function authenticateUser (email, password, done) {
await db.getParticipantByEmail(email)
.then(async (getEmailResult) => {
if (getEmailResult === undefined) {
return done(null, false, { messages: "Incorrect Username and/or Password" });
}
});
}
Using the debugger I see the message come back to /login in the info.messages. In this line in the '/login' function:
if (!user) {
req.flash('error', info.messages); //I see the message show up here
return res.redirect('/');
}
After the redirect to '/' req.flash('error') is empty.
res.render('index', { messages: req.flash("error") }); //req.flash("error") is empty here
I have no idea why this is. What am I missing?
For my project I need to use authentication using ADFS + SAML.
I have followed the code and details as suggested at
http://www.passportjs.org/packages/passport-saml and sample code at https://github.com/gbraad/passport-saml-example
During authentication, the authentication is successful, but req.user is always undefined.
When I tried to print the req, using CircularJSON.stringyfy, I can see all the details inside SAMLResponse -> 'body'.
Here is my code snippet
routes.js
module.exports = function (app,config,passport) {
app.get('/', function (req, res) {
try {
if (req.isAuthenticated()) {
console.log(req.session)
res.send('user authenticated successfully' + req.user.name) // => If I print req using CircularJSON.stringify, I can see all the details.
} else {
//res.send('User not authenticated.')
res.redirect('/login')
}
} catch (error) {
console.log('Error ' + error);
}
});
app.get('/login',
passport.authenticate(config.passport.strategy,
{
successRedirect: '/',
failureRedirect: '/login'
})
);
}
app.js
const express = require('express');
const https = require('https');
const path = require('path');
const passport = require('passport');
const morgan = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
const session = require('express-session');
const errorhandler = require('errorhandler');
const fs = require('fs');
const config = require('./config/config');
console.log('Using configuration', config);
require('./config/passport')(passport, config);
var app = express();
app.use(express.static("public"));
app.set('port', 3000);
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
app.use(morgan('combined'));
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false }));
app.use(bodyParser.json({ type: 'application/*+json' }));
app.use(session(
{
resave: true,
saveUninitialized: true,
secret: 'my_secret',
cookie: {
secure: true,
httpOnly: true
}
}));
app.use(passport.initialize());
app.use(passport.session());
require('./config/routes')(app, config, passport);
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
var my_server = https.createServer(options,app)
my_server.listen(3000);
passport.js
const SamlStrategy = require('passport-saml').Strategy;
module.exports = function (passport, config) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use(new SamlStrategy(
{
path: config.passport.saml.path,
entryPoint: config.passport.saml.entryPoint,
issuer: config.passport.saml.issuer,
},
function (profile, done) {
return done(null,
{
id: profile.uid,
email: profile.email,
displayName: profile.cn,
firstName: profile.givenName,
lastName: profile.sn
});
})
);
};
Thanks for your help.
Let me answer my own question, in case someone finds it useful.
The mistake I was making was in passport.use
When I changed it to following, it worked..
passport.use(new SamlStrategy(
{
path: config.passport.saml.path,
entryPoint: config.passport.saml.entryPoint,
issuer: config.passport.saml.issuer,
cert: config.passport.saml.cert,
},
function (profile, done) {
return done(null,
{
id: profile['nameID'],
email: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'],
displayName: profile['http://schemas.microsoft.com/identity/claims/displayname'],
name: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'],
lastName: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname']
});
})
);
I'm working with a back-end made with Node.js and front-end made with Angular.
I use express-sessions for login, at first, it returns the correct req.session.usuario. But in the next POST request for checking if the user is logged in it returns undefined.
Here is the app.js of node
var express = require('express');
var path = require('path');
var logger = require('morgan');
var cors = require('cors');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');
var usersRouter = require('./routes/users');
var instructorRouter = require('./routes/instructor');
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser());
app.use(cors( {
credentials: true,
origin: 'http://localhost:4200'
}));
app.use(session({
secret: "Shh, es un secreto",
resave: false,
saveUninitialized: true
}))
app.use('/', indexRouter);
app.use('/auth', authRouter);
// app.use('/usuario', authMiddleware.estaLogueado ,usersRouter);
app.use('/usuario', usersRouter);
app.use('/instructor', instructorRouter);
...
module.exports = app;
This is my function for the route in the auth.js
router.post('/login', (req, res, next) => {
const username = req.body.username.toLowerCase();
Usuario.findOne({
where: {
username: username
}
}).then((usuario) =>{
if (usuario) {
bcrypt.compare(req.body.password, usuario.password, function(errp, result) {
if(result){
req.session.usuario = usuario.id; // Saves the session
console.log("La sesion " + req.session.usuario);
res.json({username: usuario.username, color: usuario.color});
} else {
next(new Error("ContraseƱa incorrecta"));
}
});
}else{
next(new Error("Usuario invalido"));
}
}, err => {
next(new Error("Usuario invalido: " + err));
});
});
And this is the function that I use for checking if is logged in:
router.post('/logged', (req, res) => {
console.log("intento: " + req.session.usuario) // here it shows undefined
req.session.usuario ? res.json({loggedIn: true}) : res.json({loggedIn: false});
})
In Angular these are the two functions that I use, the first one is the login (which works) and the second one is the logged that returns false all the time:
login(user, pass) {
return this.http.post(server + '/auth/login',
{username: user, password: pass},
{withCredentials: true}
)
}
logged(){
return this.http.post(server + '/auth/logged', {withCredentials: true})
}
In the actual component I do:
this.authservice.logged().subscribe( (data) => {
this.loggedIn = data['loggedIn'];
console.log(data['loggedIn']);
});
What is the problem here?
I finally got the answer! I was doing everything ok but the second request was actually not including the withCredentials json as options, it was passed as body so I added empty brackets to simulate the body and now it works like a charm!
Original Question
I am using passport.js to do authentication in express, when I use req.flash('message', 'message content') in passport strategy, the flashed information is not under the normal session but 'sessions' and when I tried to retrieve the flashed message using req.flash(), it's an empty array.
I printed out the req
, it looks like this:
MemoryStore {
_events:
{ disconnect: [Function: ondisconnect],
connect: [Function: onconnect] },
_eventsCount: 2,
_maxListeners: undefined,
sessions:
{ gzNcx9b8rcWfDtJm03VnNJfhsNW8EJ7B:
'{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"flash":{"message":["emails has been taken, choose another one!"]}}' },
generate: [Function] },
sessionID: 'ffSa89VCV0Mj6uKLrEPMAdNMGLR2I5ML',
session:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } },
_passport:
Somehow it opens a new session after redirecting to /api/signupFail. Could anyone help me with this?
Here is my middleware setup:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var logger = require('morgan');
var passport = require('passport');
require('./config/passport')(passport);
var cors = require('cors');
var session = require('express-session');
var flash = require('connect-flash');
var app = express();
var corsOptions = {
origin: 'http://localhost:4200',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.use(cors(corsOptions));
app.use(logger('dev'));
app.use(cookieParser('Thespywhodumpedme'));
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
var goalsRoute = require('./routes/goalsRoute');
var userRoute = require('./routes/userRoute');
// required for passport
app.use(flash());
app.use(session({ secret: 'keyboard cat',resave: true, saveUninitialized:true})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
// use connect-flash for flash messages stored in session
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/signup', passport.authenticate('local-signup', {
successRedirect: '/api/user/suctest',
failureRedirect: '/api/signupFail',
failureFlash: true
}));
app.get('/api/signupFail', (req, res, next) => {
console.log(req.flash('message')); //this is an empty array
res.status(403).send('fail');
})
Here is my strategy setup:
module.exports = function(passport) {
passport.serializeUser((user, done) => {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser((id, done) => {
db.User.getUserById(id, (err, result) => {
done(err, result[0]);
});
});
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
if(!email || !password ) { return done(null, false, req.flash('message','All fields are required.')); }
var salt = '7fa73b47df808d36c5fe328546ddef8b9011b2c6';
db.User.getUserByEmail(email, function(err, rows){
if (err) {
return done(req.flash('message',err));
}
if(rows.length > 0){
return done(null, false, req.flash('message',"emails has been taken, choose another!"));
}
salt = salt+''+password;
var encPassword = crypto.createHash('sha1').update(salt).digest('hex');
var newUser = {
name: req.body.name,
email: email,
password: encPassword,
sign_up_time: new Date()
}
db.User.addOneUser(newUser, (err, result) => {
db.User.getUserByEmail(email, (err, result) => {
return done(err, result[0]);
})
});
});
}));
};
Update
At first, I thought it has something to do with flash, but then after printing session out, I found that a new session is created after redirecting. I thought it has something to do with the backend setup. Accidentally, I found this problem doesn't exist when I sent the request from postman. That's when I figured out it might have something to do with Angular which is listening on port 4200 while express listening on port 3000. I was sending the request to port 3000 by hardcoding the port number in httpClient. After I set up a proxy that redirects all API call to port 3000. Everything works just fine.
OK, it turns out that it has nothing to do with the backend. Everything works just fine when I sent the request through postman. The problem is with the frontend, I am using Angular 6, Angular is listening on port 4200 while express listening on port 3000. I set up a proxy in Angular that redirects all API call to localhost: 3000 and the session is persistent.
I have some problems with the express session where I cannot retrieve my session variable that I had stored previously. Below are parts of my codes that I had written.
server.js
let express = require('express'),
path = require('path'),
bodyParser = require('body-parser'),
cors = require('cors'),
config = require('./config/database'),
expressSession = require('express-session'),
uid = require('uid-safe'),
db;
let app = express();
//Import Routes
let auth = require('./routes/auth'),
chimerListing = require('./routes/chimer-listing'),
brandListing = require('./routes/brand-listing');
//Specifies the port number
let port = process.env.PORT || 3000;
// let port = 3000;
// Express session
app.use(expressSession({
secret: "asdasd",
resave: true,
saveUninitialized: false,
cookie: {
maxAge: 36000000,
secure: false
}
}));
//CORS Middleware
app.use(cors());
//Set Static Folder
var distDir = __dirname + "/dist/";
app.use(express.static(distDir));
//Body Parser Middleware
app.use(bodyParser.json());
//MongoDB
let MongoClient = require('mongodb').MongoClient;
MongoClient.connect(config.database, (err, database) => {
if (err) return console.log(err)
db = database;
//Start the server only the connection to database is successful
app.listen(port, () => {
console.log('Server started on port' + port);
});
});
//Make db accessbile to routers;
app.use(function(req, res, next) {
req.db = db;
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.set('Access-Control-Allow-Headers', 'Content-Type');
next();
});
//Routes
app.use('/login', auth);
app.use('/user-listing', userListing);
app.use('/brand-listing', brandListing);
//Index Route
app.get('/', (req, res) => {
res.send('Invalid Endpoint');
});
genuuid = function() {
return uid.sync(18);
};
auth.js
let express = require('express'),
router = express.Router(),
db;
//Login Router for chimer
router.post('/chimer', (req, res, next) => {
db = req.db;
// let client = req.client;
db.collection('chimeUser').find({
Username: req.body.username,
Password: req.body.password
}).toArray().then(function(docs) {
//If there is such user
if (docs.length >= 1) {
req.session.chimerId = docs[0]._id;
console.log(req.session);
req.session.save(function(err) {
// session saved
if (err)
console.log(err)
res.json({
success: true,
chimerId: docs[0]._id
//objects: docs
});
})
} else {
res.json({
success: false,
//objects: docs
})
}
});
});
//Login Router brand
router.post('/brand', (req, res, next) => {
db = req.db;
db.collection('brand').find({
Username: req.body.username,
Password: req.body.password
}).toArray().then(function(docs) {
req.session.brand = docs;
console.log(req.session.brand);
//If there is such user
if (docs.length >= 1) {
res.json({
success: true,
//objects: docs
})
} else {
res.json({
success: false,
//objects: docs
})
}
//db.close()
});
});
});
module.exports = router;
user-listing.js
let express = require('express'),
moment = require('moment'),
router = express.Router(),
// ObjectID = require('mongodb').ObjectID,
db, client;
// let applyListing = require('../models/chimer-listing');
//Retrieve All Listing
router.get('/getAllListing', (req, res, next) => {
db = req.db;
console.log(req.session)
db.collection('listing').find().toArray().then(function(listing) {
//If there is any listing
if (listing.length >= 1) {
res.json({
success: true,
results: listing
})
} else {
res.json({
success: false,
})
}
//db.close()
});
});
module.exports = router;
So in my server.js, I have three routes file which is auth, user-listing, and brand-listing.
Firstly, a user will need to login with the web application which is developed in angular2 and this will trigger the auth route. It will then check for the credentials whether does it exist in the database if it exists I will then assign an ID to req.session.chimerId so that in other routes I will be able to use this chimerId.
Next, after the user has logged in, they will then retrieve an item listing. The problem arises where I can't seem to retrieve the req.session.chimerId that I had previously saved. It will be undefined
NOTE: I tried this using Postman and the browser. In the Postman it works, I am able to retrieve back the req.session.chimerId whereas when I use the angular2 application to hit the endpoints req.session.chimerId is always null