Passport: Maintaining Cross Domain session with passport.js

Created on 28 Aug 2015  ·  9Comments  ·  Source: jaredhanson/passport

I am using nodeJs for server and with express and passport as authentication middleware. My front end app is running on angular on localhost:9000 and node app on localhost:3000 . I found that the passport is not able to save session in cross domain . on every page request on my client app , passport create a new session for me instead of maintaining a single session . Also after login on login page it create a session in mongo with user detail available in it , but on second request it never resolve the same session cookie for me .

All 9 comments

I'm having the same issue, did you find a solution?, if not I would really appreciate any help, the login works but the session is not maintained, I have already made sure all headers are fine and that the cookie actually gets to the backend, but it still doesn't work

Have you been able to find a solution for this? I'm facing the same problem.

+1

Did you set cross domain headers on your app ?

I have tried

var cors = require('cors');
var app = express();
app.use(cors());

and

app.all('*', function(req, res, next) {
      res.header("Access-Control-Allow-Origin", req.headers.origin); // also  tried "*" here
      res.header("Access-Control-Allow-Headers", "X-Requested-With");
      res.header('Access-Control-Allow-Headers', 'Content-Type');
      next();
});

But neither of them worked.

did anyone solve this ?

+1

You'll need SSO (single sign on) implementation for that using tokens exchange,
however if you use subdomains and not two different domains it is possible to get a shared session by setting up a global session store (e.g with redis) and setting a cookie to the root domain like this :

const expressSession = require('express-session');
const redisStore = require('connect-redis')(expressSession);
...
app.use(
  expressSession({
    store: new redisStore({ host: 'localhost', port: 6379 }),
    secret: 'keyboard cat',
    cookie: { domain: 'domain.com' },
    resave: false,
    saveUninitialized: false
  })
);
app.use(passport.initialize());
app.use(passport.session());

This way you'll get shared session for both app1.domain.com and app2.domain.com

However for cross domains you'll need SSO implementation with another authorization server (e.g sso.otherdomain.com) which will exchange tokens for each domain after authenticating to to the authentication server. I'm trying to find an example/documentation for passport with SSO for myself as well but couldn't find anything so far (closest I've found is this)

LOL.
Because of the pure documentation, I spent the whole day trying to understand why authentication works and I'm receiving the user data on the initial call, but after the page refresh session is not storing.

This article is trying to explain: PassportJS — The Confusing Parts Explained

TL;DR

Just defined serializeUser and deserializeUser methods one next way:

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(id, done) {
  // NOTE: second true param is making 'hack'
  // so the session will be saved after a page refresh, but it will be destroyed after a server refresh
  done(null, true);
});

DO NOT BLINDLY COPY THE CODE - DO NOT USE IT IN PRODUCTION

What do we missed about passport-js?

Step 1: We are defining our strategy like:

  passport.use(
    new LinkedInStrategy(
      {
         // config params
      },
      function(accessToken, refreshToken, linkedInProfile, done) {
        process.nextTick(function() {
          return done(null, linkedInProfile); // Authenticated profile data from LinkedIn
        });
      }
    )
  );

Step 2: When done method is invoked, as I understood, passport is invoking serializeUser and passing linkedInProfile as first argument (because we passed it previously into done method):

passport.serializeUser(function(linkedInProfile, done) {
  done(null, linkedInProfile.id);
});

So, the session on the server is established and the session is using linkedInProfile.id for request identification

Step 3: On next request from your front-end (client app), you are sending linkedInProfile.id with the request (because passport established all for you).

So, on every request to the server, passport invokes deserializeUser method. In that method you can:
1) Close the session by invoking

  passport.deserializeUser(function(id, done) {
      done(null, false);
  });

2) Keep session alive, by confirming that this is real session
To confirm session, you need to invoke done method with correct params, to just pass true for bypassing it:

passport.deserializeUser(function(linkedInProfileId, done) {
  done(null, true);
});

NOTE: that the first argument in deserializeUser is what you sent to done method in serializeUser.
*ALSO: when if you are using nodemon and you are changing the code, please notice, that on each page server is refreshing and express-session is creating a new session for you. So that is not passport is changing session - the sessionId is changed by nodemon or express. You can check it like:

app.get('*', (req, res) => { console.log('sessionID', req. sessionID); });

After you make changes into the code and save file - server will be restarted and sessionID will be updated, so you need to authenticate again.

Sum-up:

You can bypass session, but please implement correct support for serializeUser and deserializeUser methods for final usage.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ksmithut picture ksmithut  ·  6Comments

callumacrae picture callumacrae  ·  5Comments

franckl picture franckl  ·  5Comments

AdityaAnand1 picture AdityaAnand1  ·  4Comments

ginovski picture ginovski  ·  6Comments