I am working on a MEAN application, I am using Angular 4 for my project. For authentication, I have implemented the Passport js Local-strategy
. And I am maintaining persistent session using Express-session
. Things are working fine till here.
The Problem
In the same domain session
works fine and I am able to authenticate the user. But in cross-domain, I am not able to maintain the session. It generates a new session id for each new request in cross-domain.
I then tried Passport-jwt
but the problem with it is I don’t have the control over user session. I mean I can’t logout the user from the server if he is inactive or even on server re-start also the token
don’t get invalid.
So in simple words, I am looking for an authentication solution in Node js (Express js) in which I can manage authentication in cross-domain.
I have already seen some blog post and SO questions like this, but it doesn’t help.
Thank you.
EDIT
Should I write my own code to achieve this? If so I have a plan.
My basic plan is:
- The user will send credentials with the login request.
- I will check for the credentials in the database. If credentials are valid, I will generate a random token and save it to the database, in the user table and the same token I will provide to the user with success response.
- Now, with each request user will send the token and I will check the token for each request in the database. If the token is valid then I will allow the user to access the API otherwise I will generate an error with 401 status code.
- I am using Mongoose (MongoDB) so I will be ok to check the token in each request (performance point of view).
I think this is also a good idea. I just want some suggestions, whether I am thinking in right direction or not.
What I will get with this:
- The number of logged in user in the application (active sessions).
- I can logout a user if he is idle for a certain interval of time.
- I can manage multiple login session of the same user (by doing an entry in the database).
- I can allow the end user to clear all other login sessions (like Facebook and Gmail offers).
- Any customization related to authorization.
EDIT 2
Here I am shareing my app.js
code
var express = require('express');
var helmet = require('helmet');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var dotenv = require('dotenv');
var env = dotenv.load();
var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var session = require('express-session');
var cors = require('cors');
var databaseUrl = require('./config/database.js')[process.env.NODE_ENV || 'development'];
// configuration
mongoose.connect(databaseUrl); // connect to our database
var app = express();
// app.use(helmet());
// required for passport
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
if ('OPTIONS' == req.method) {
res.send(200);
} else {
next();
}
});
app.use(cookieParser());
app.use(session({
secret: 'ilovescotchscotchyscotchscotch', // session secret
resave: true,
saveUninitialized: true,
name: 'Session-Id',
cookie: {
secure: false,
httpOnly: false
}
}));
require('./config/passport')(passport); // pass passport for configuration
var index = require('./routes/index');
var users = require('./routes/user.route');
var seeders = require('./routes/seeder.route');
var branches = require('./routes/branch.route');
var companies = require('./routes/company.route');
var dashboard = require('./routes/dashboard.route');
var navigation = require('./routes/navigation.route');
var roles = require('./routes/role.route');
var services = require('./routes/services.route');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
require('./routes/auth.route')(app, passport);
app.use('/', index);
app.use('/users', users);
app.use('/seed', seeders);
app.use('/branches', branches);
app.use('/companies', companies);
app.use('/dashboard', dashboard);
app.use('/navigation', navigation);
app.use('/roles', roles);
app.use('/services', services);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
res.status(404).send({ status: 'NOT_FOUND', message: 'This resource is not available.'});
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
let errorObj = {
status: 'INTERNAL_SERVER_ERROR',
message: 'Something went wrong.',
error: err.message
};
res.status(err.status || 500).send(errorObj);
});
module.exports = app;
EDIT 3
For those who don’t understand my problem. Explaining the problem in
simple words:
- My Express server is running on port 3000.
- In order to consume any API from the server, a user must be logged in.
- When a user gets logged in from
localhost:3000
, the server checks the credentials(using Passport-local) and returns a token in the response header. - Now after login, when a user hits any API from
localhost:3000
, a predefinedHeader
comes withpassport-session
and then passport verifies the user session usingreq.isAuthenticated()
and all the things works as expected. - When a user gets logged in from
localhost:4000
and the server send a token in response header (same aslocalhost:3000
). - When after successful login, the user hits any API from
localhost:4000
the passport js functionreq.isAuthenticated()
returnsfalse
. - This was happening because in cross-domain the
cookie
doesn’t go to the server we need to setwithCredentials
header totrue
at the client side. - I have set
withCredentials
header totrue
but still at the server thereq.isAuthenticated()
is returningfalse
.
5
Answers
If you want to use sessions (ie. instead of jwt, etc) I think by default they are just in-memory so it will not work as your application scales to multiple hosts. It is easy to configure them to persist though.
See
https://github.com/expressjs/session#compatible-session-stores
You might have tried with passport-jwt. It generates tokens as per the JWT protocol on login. Your requirement is to blacklist the generated token when you logout. To achieve that, you can create a collection in mongodb named “BlacklistToken” with fields userid and token. When the user logs out, you can insert the token and userid in the collection. Then write a middleware to check whether the token is blacklisted or not. if it is redirect to login page.
did you already take a look here:
You may try this (allow any public IP) :
It is normal that the second server re-create a new session, because assuming that you use Express-session, and according to the documentation:
Which mean that you need to find a way to synchronize servers session data …
Assuming that you find a method to do that, when you will try to connect, both server will retrieve the same user session data and the second will not have to create a new session…
If I understand the problem correctly here, you want the user’s session to be stateless on the server. So that whenever the user logs in, the session can be re-used in any instance of the server when you scale your application, or even if you were to just reboot your application.
To achieve this, what you need is to configure the
express-session
with a database solution. You can do this withmongo
using this package https://github.com/jdesboeufs/connect-mongo.However, best practice is to use something a bit more robust for this sort of use-case, like
redis
using this package https://github.com/tj/connect-redis.One possible solution to get around CORS/cookie/same-domain problems is to create proxy server that will mirror all requests from
localhost:3000/api
tolocalhost:4000
, and then uselocalhost:3000/api
to access the API instead oflocalhost:4000
.Best way for production deployment is to do it on your web server (nginx/apache).
You can also do it in node via
express
andrequest
modules, or use some ready made middleware like this one:https://github.com/villadora/express-http-proxy
Solution with this middleware is pretty straightforward: