I am new to express and I am trying to create session when user logs in to my app.I am using passport for authentication. To login user I am creating basic strategy and on success of basic strategy I am creating a JWT token which I am storing at client side in cookie and I use JWT strategy for subsequent requests.
But I notice that express-session is creating one session when I am logging in which has Basic header and another session for subsequent calls which has JWT header.
Here is my code where I am saving session on login
signin = function(req, res, next) {
passport.authenticate('basic', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
return res.status(401).json({ error: 'message' });
}
var token = jwt.encode({ user: user}, config.db.secret);
res.cookie('token', token, { maxAge: 900000, httpOnly: true, path: '\/', Secure: true });
res.status(200).json({ success: true, firstName: user.firstName });
delete user.password;
req.session.user = user;
req.session.save();
})(req, res, next);
};
But when I debug this code it shows one sessionID in req.sessionID and it show different sessionID in req.sessionID in the following code which as JWT authentication
listProducts = function(req, res) {
debugger;
//here req.session.user is undefined which I have saved at login. and sessionID is also different
res.json({ demo: 'response' });
};
I am expecting it to be same sessionID throughout the life cycle till user logs out of my app.
Why is this exactly happening? What is the solution to it?
You are sending the response before saving the session.
Try saving the session, then sending the response instead.
express-session modifies res.end to make it perform express-session specific tasks introducing the sequential coupling you were victim of: https://github.com/expressjs/session/blob/master/index.js#L249
Related
I wanted to use my login data (username, ID) and store them in other tables so that I could access the other data stored in those tables. I am using Node.js and express for my server, and for the session I am using the express-session module.
Here is the app.js and the session middleware and its default options
import session from 'express-session';
app.use(session({
name:'test',
secret: "thisismysecrctekeyfhrgfgrfrty84fwir767",
saveUninitialized: true,
resave: false
}));
//some other stuff
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((_, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
app.use((req,res,next) => {
console.log('req.session',req.session);
next();
});
After this I use router for all my routes,
Here is the login route where I save my username into the session
router.post('/login', (req, res, next) => {
// checks if email exists
User.findOne({ where : { //
name: req.body.name, //
}})
.then(dbUser => {
if (!dbUser) {
return res.status(404).json({message: "user not found"});
} else {
// password hash
bcrypt.compare(req.body.password, dbUser.password, (err, compareRes) => {
if (err) { // error while comparing
res.status(502).json({message: "error while checking user password"});
} else if (compareRes) {
// password match
const token = jwt.sign({ name: req.body.name }, 'secret', { expiresIn: '1h' });
const sessName= req.body.name;
req.session.name=sessName;
res.status(200).json({message: "user logged in", "token": token});
} else { // password doesnt match
res.status(401).json({message: "invalid credentials"});
};
});
};
})
.catch(err => {
console.log('error', err);
});
});
After the user enters the Login info it goes from the login route to the Auth route
router.get('/private', (req, res, next) => {
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: 'not authenticated' });
};
const token = authHeader.split(' ')[1];
let decodedToken;
try {
decodedToken = jwt.verify(token, 'secret');
} catch (err) {
return res.status(500).json({ message: err.message || 'could not decode the token' });
};
if (!decodedToken) {
res.status(401).json({ message: 'unauthorized' });
} else {
res.status(200).json({ message: `here is your resource ${req.session.name}` });
};
});
If I console log the session it shows the username on the terminal, for other routes when I store some data onto the session and access it from other routes it works fine, but when I store the username from the login route it doesn't show up when I try to access it.
router.get('/session', (req,res,next)=>{
if (req.session.name) {
res.json({name: req.session.name})
} else {
res.json({name: null});
}
})
I am new to Node.js so it would be helpful if anyone could answer this.
After
const sessName= req.body.name;
req.session.name=sessName;
the name gets saved in the session from the login route, but it shows me a null value when I access it from other routes. Plus is it normal for the session to end and the username to disappear after I restart my server.
Since both /login and /private look like they are being made by code, the problem is probably caused by that code not capturing the session cookie, retaining it and making sure it is sent with all subsequent requests on behalf of this same user.
That session cookie is the key to making express-session work. Upon first engagement with a client, the express-session middleware sees that there is no session cookie attached to this request. It creates a new one with an encrypted unique key in it, adds that key to the internal session store and returns that cookie with the http response.
For the session to work, the client that receives that cookie must retain it and send it with all future http requests from this client (until it expires). Since these http requests to /login and /private are being made programmatically, your code has to be set up properly to retain and send this cookie. It will depend upon what http library you are using to make these http requests for how exactly you go about retaining and resending the session cookie.
If these are made from Javascript running in a browser in a web page, then the browser will retain the cookie for you and you will just need appropriate settings on the http requests to make sure the cookie is sent. If you are doing cross-origin requests, then things get a little more complicated.
If these are made from your own code in your own app, then the precise steps to include the cookie will depend upon what http library you're using. Many http libraries have something often referred to as a cookie jar that can be associated with a given request/response to help maintain and send cookies.
Now that you've shown your client code, that code is using fetch() to make these calls. That's the http request library your client is using.
Because it appears this may be a cross-origin request, to be sure that cookies are sent with requests made by fetch(), you can add the credentials: "include" option like this:
fetch(`${API_URL}/private`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
credentials: "include",
}).then(...)
If fetch() doesn't send credentials (which include cookies), then your session cookie won't be sent and your server will think it has to create a new session on each new request so your session will never be maintained from one request to the next.
I am having troubles understanding how passport.js authentication flow works.
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
done(null, id);
});
app.use(
cookieSession({
name: 'session',
maxAge: 60 * 60 * 1000,
keys: [config.COOKIE_KEY_1, config.COOKIE_KEY_2],
})
);
app.use(passport.initialize());
app.use(passport.session());
// this is a middleware for my protected routes
const checkLoggedIn = (req, res, next) => {
const isLoggedIn = req.isAuthenticated() && req.user;
if (!isLoggedIn) {
return res.status(401).json({
error: 'you muse log in!',
});
}
next();
};
I have read a few articles. They say the "user.id" in passport.serialize is stored in req.session & the "id" in passport.deserialize is the same as "user.id".
My questions are:
is "user.id" sent to the browser along with the cookie?
How does passport verify cookie when there is a request to the server to get the id out of it?
Does the client know about "req.session" & can the client access this data?
Thank you very much!
The contents of the session are only known to the server and are always available in req.session. A cookie is sent between server and client which ensures that req.session is always the session that belongs to the client that sent the request req. In other words: When the code on your server accesses req.session, it will always access the session contents of the user who made the current request.
But the session contents are not accessible by the client (and nor is the session cookie if it is configured as httpOnly).
So the answers to your questions are:
No
Don't know details, but passport can access req.session.user.id
No
I'm protecting an API in my web app using passport. Before hitting the resource, I want the user(from oauth client) to login/authorize first.
Have tried both LocalStrategy and BasicStrategy - their implementation are almost 100% the same, just look for a user by email and verify password. If I use BasicStrategy, the browser will bring up a dailog saying 'Authentication Required' and ask for username & password. However if I use LocalStrategy, it just says 'Unauthorized', and no chance to login.
So my questions are:
How does browser decide to bring up the login dialog, or is it done by BasicStrategy?
Is it possible to show a login page with some UI, instead of the simple dialog?
Note that it's part of OAuth process so I don't really want to redirect to the login page.
Passport is used only for authentication. Later after one needs to maintain session , to check if user is logged in or not.
So you can make a middleware call before every route.
Middlleware checking if user is logged in or not .
'use strict';
var express = require('express');
module.exports = {
isLoginCheck : function (request, response, next) {
if(!request.session.user && request.path != '/login'){
response.redirect('/login');
}else{
next();
}
},
};
In routes file import the middleware,
var express = require('express'),
indexController = require('./../controller/index'),
middleware = require('./../middleware/index'),
passport = require('passport'),
router = express.Router();
router.get('/addUser', indexController.addUser);
router.post('/saveUser', indexController.saveUser);
router.use(middleware.isLoginCheck);
router.get('/', indexController.index);
router.get('/login', indexController.login);
router.post('/login', function(request, response, next){
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
var message = "Invalid credentials";
return response.render('login',{message: info.message, userLoggedIn : null});
}
request.logIn(user, function(err) {
if (err) { return next(err); }
request.session.user = request.user;
response.redirect('/userList');
});
})(request, response, next);
});
Note : in the success we are saving the verified user in session , which will persist till the server is running. Middleware above we are checking for the session object.
Turned out the Authentication dialog is the sequela of response header:
The browser detects that the response to my call on the XMLHttpRequest object is a 401 and the response includes WWW-Authenticate headers. It then pops up an authentication dialog asking, again, for the username and password.
Here's a very helpful post.
As for the standalone signin page, because it's part of OAuth process, it's better to use express's redirect.
Background
I have a MEAN application with CRUD capabilities fully tested with postman. I have been trying to persist login for quite some time now with no luck. I have read and tried the following
passport docs
toon io's blog about login
Express session
Scotch io, node auth made easy
Plus a buch of other light reading materials (Lots of SO questions)
But I have only been able to register and log a user in, not persist login with a session.
My App
Here is a link to the full github repo (if you are looking for the latest changes check develop branch)
My Understanding of Auth/Login
Here is my understanding of user login with code examples from my project and screenshot of postman results as well as console logs.
Passport setup
I have the following auth.js file, it configs passport
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
This gets called in the server file like
//code before
var user = require('./models/user.js');
var auth = require('./modules/auth.js')(app, user);
// code after
Routing for login
In my routes I have the login route as follows
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({
err: info
});
}
req.logIn(user, function(err) {
if (err) {
return res.status(500).json({
err: 'Could not log in user'
});
}
res.status(200).json({
status: 'Login successful!'
});
});
})(req, res, next);
});
This route works as tested with postman. I enter the details 'joe' and 'pass' and get the following response.
When this route is hit we can also see in the console that the user is serialized.
So what next?
This is where I get lost. I have a few questions.
Is the user now in a session on my server?
Should I send the req.session.passport.user back to the client?
Do I need the session ID on all future requests?
Testing the Session
I have a second route setup for testing the session it is as follows
router.get('/checkauth', passport.authenticate('local'), function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
The part passport.authenticate('local') (I thought) is there to test if the user session exists before giving access to the route but I never get a 200 response when I run this, even after a login.
Does this route expect a req.session.passport.user passed in the head or as a data argument on a http request that requires auth?
If I missed anything or am understanding something wrong please tell me, any input is appreciated. Thanks all.
Is the user now in a session on my server?
No, You need to use the express-session middleware before app.use(passport.session()); to actually store the session in memory/database. This middleware is responsible for setting cookies to browsers and converts the cookies sent by browsers into req.session object. PassportJS only uses that object to further deserialize the user.
Should I send the req.session.passport.user back to the client?
If your client expects a user resource upon login, then you should. Otherwise, I don't see any reason to send the user object to the client.
Do I need the session ID on all future requests?
Yes, for all future requests, the session id is required. But if your client is a browser, you don't need to send anything. Browser will store the session id as cookie and will send it for all subsequent requests until the cookie expires. express-session will read that cookie and attach the corresponding session object as req.session.
Testing the Session
passport.authenticate('local') is for authenticating user credentials from POST body. You should use this only for login route.
But to check if the user is authenticated in all other routes, you can check if req.user is defined.
function isAuthenticated = function(req,res,next){
if(req.user)
return next();
else
return res.status(401).json({
error: 'User not authenticated'
})
}
router.get('/checkauth', isAuthenticated, function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
As #hassansin says you need to use a middleware that implement session management. The passport.session() middleware is to connect the passport framework to the session management and do not implement session by itself. You can use the express-session middleware to implement session management. You need to modify your auth.js in the following way
var passport = require('passport');
var session = require('express-session');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(session({secret: 'some secret value, changeme'}));
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
Notice that in this case the session engine is using the in memory store and it didn't work if you scale your application and apply load balancing. When you reach this development state something like the connect-redis session store will be needed.
Also notice that you need to change the secret value used on the session midleware call and use the same value on all application instances.
As per the passport documentation, req.user will be set to the authenticated user. In order for this to work though, you will need the express-session module. You shouldn't need anything else beyond what you already have for passport to work.
As far as testing the session, you can have a middleware function that checks if req.user is set, if it is, we know the user is authenticated, and if it isn't, you can redirect the user.
You could for example have a middleware function that you can use on any routes you want authenticated.
authenticated.js
module.exports = function (req, res, next) {
// if user is authenticated in the session, carry on
if (req.user) {
next();
}
// if they aren't redirect them to the login page
else {
res.redirect('/login');
}
};
controller
var authenticated = require('./authenticated');
router.get('/protectedpage', authenticated, function(req, res, next) {
//Do something here
});
I don't know of a way to check all existing sessions, but Passport is handling the issuing of session ids. Try checking that you have req.user on your test endpoint after logging in
Auth0 documentation describes how to set up express-jwt middleware to protect endpoints. The trouble is that the documentation doesn't seem to cover how you get a valid JWT in the first place.
On the angular side, there's documentation on using angular plugins to implement a login page, that's fine. How would one implement a route using express that would take a username/password and return to the client the appropriate JWT such that subsequent requests would be authorized?
I think I may be missing a basic concept about JWT here; via Auth0, when using Username-Password-Authentication, my guess is that Auth0 acts as the repo for those credentials. There's documentation out there about wiring passport to auth0 and JWT, the problem with those is that this documentation assumes that the username/password database is some MongoDB instance locally...I want to avoid that type of setup which was an initial attraction with auth0.
Are there sample projects that cover this, showing how to get a valid JWT on a back-end, without some separate front-end angular app requesting it first?
I use passport.js built in local strategy for authentication and store user information in a JWT that I read on routes that require authorization.
User id's can be serialized/deserialized into and out of the express sessionto obtain the user identifier using the auth token (JWT) in the request. This is in my opinion the best approach since it limits the amount of data stored on the client and provides better security than storing any user information. Here's an example of this in express:
//Set a session secret
var secrets = { sessionSecret: process.env.secret || 'my secret string'};
//Require express-jwt and set a secret for the cookie
var expressJwt = require('express-jwt');
var validateJwt = expressJwt({ secret: secrets.sessionSecret });
//Returns a jwt token signed by the app secret
var signToken = function(id) {
return jwt.sign({
id: id
}, secrets.sessionSecret, {
expiresInMinutes: 60 * 24 // 24 hours
});
};
//Set token cookie directly
var setTokenCookie = function(req, res) {
if (!req.user) {
return res.status(404).json({
message: 'Error during user validation'
});
}
var token = signToken(req.user.id, req.user.role);
res.cookie('token', JSON.stringify(token));
};
//Check to see if user is authenticated (call this when a route is requested)
var isAuthenticated = function(req, res, next) {
// allow access_token to be passed through query parameter as well
if (req.body && req.body.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.body.access_token;
}
// Validate jwt token
return validateJwt(req, res, next);
};
You can use these methods as middleware in express. Say the above code was token.js, you can force it to execute on each request to a route like this:
app.get('/employee', token.isAuthenticated, employeeController.getEmployees);
I haven't worked with angular but it works great on the backbone projects i've worked on and this process should work with any browser based client that can supply a X-auth cookie on each request. You can do this by using the ajax setup:
$(document).ajaxSend(function(event, request) {
var token = readCookie('token');
if (token) {
request.setRequestHeader('authorization', 'Bearer ' + token);
}
});
Here is an example of middleware that validates a users login and returns a token to the client that can be used on subsequent requests:
var validateLogin = function (req, res, next) {
var username = req.params.username;
// Authenticate using local strategy
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(404).json({
info: [{
msg: info.message
}]
});
}
// Send user and authentication token
var token = token.signToken(user.id, user.role);
res.cookie('token', token);
res.render('index', {token: token, user: user});
})(req, res, next);
};
#FrobberOfBits
This is to answer the follow-up Q posted by FrobberOfBits on Feb 6, 2016 at 3:04
I use auth0 for local + social media authentication.
The way auth0 works is, you hand over the approach to authenticate to auth0 ...either it be local with db or social media.
It is a bundled approach where local db and social media authentication is all bundled and provided as a service to you by auth0.
Hope this helps.