Why React app doesn't keep nodejs server login session?

Viewed 361

Greetings. I've a NodeJS server as:

app.js

'use strict';
const http = require('http');
const express = require('express');
const session = require('express-session');
const pino = require('pino');
const pinoHttp = require('pino-http');

const passport = require('./config/passport');
const httpErrors = require('http-errors');

module.exports = function main(options, cb) {
    // Set default options
    const ready = cb || function() {};
    const opts = Object.assign(
        {
            // Default options
        },
        options
    );

    const logger = pino();

    // Server state
    let server;
    let serverStarted = false;
    let serverClosing = false;

    // Setup error handling
    function unhandledError(err) {
        // Log the errors
        logger.error(err);

        // Only clean up once
        if (serverClosing) {
            return;
        }
        serverClosing = true;

        // If server has started, close it down
        if (serverStarted) {
            server.close(function() {
                process.exit(1);
            });
        }
    }
    process.on('uncaughtException', unhandledError);
    process.on('unhandledRejection', unhandledError);

    // Create the express app
    const app = express();

    // Common middleware
    app.use(pinoHttp({ logger }));

    app.use(session({
        resave: false,
        secret: 'supersecretamazingpassword',
        saveUninitialized: true
    }));
    app.use(passport.initialize());
    app.use(passport.session());
    app.use(express.urlencoded({ extended: false }));
    app.use(express.json());

    require('./routes')(app, opts);

    app.use(function fourOhFourHandler(req, res, next) {
        next(httpErrors(404, `Route not found: ${req.url}`));
    });

    app.use(function fiveHundredHandler(err, req, res, next) {
        if (err.status >= 500) {
            logger.error(err);
        }
        res.status(err.status || 500).json({
            messages: [
                {
                    code: err.code || 'InternalServerError',
                    message: err.message
                }
            ]
        });
    });

    server = http.createServer(app);
    server.listen(8080);
};

routes.js

'use strict';

const pino = require('pino');
const passport = require('./config/passport');

module.exports = function(app, opts) {
    const logger = pino();

    app.use((req, res, next) => {
        res.header('Access-Control-Allow-Origin', req.header('origin'));
        res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
        res.header('Access-Control-Allow-Credentials', 'true');
        if (req.method == 'OPTIONS') {
            res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET');
            return res.status(200).json({});
        }
        logger.info(req)
        logger.info(res)
        next();
    });

    app.get('/auth/login', passport.authenticate('saml', {
            successRedirect: '/',
            failureRedirect: '/auth/failed'
        }),
        (req, res) => {
            logger.info("Requesting: /auth/login");
            return res.redirect('http://localhost:8088');
        }
    );

    app.get('/auth/failed', (req, res) => {
        logger.info("Requesting: /auth/failed");
        return res.status(500).json({
            message: 'Un error ha ocurrido'
        });
    });

    app.post('/auth/callback', passport.authenticate('saml'), (req, res) => {
        logger.info("Requesting: /auth/callback");
        return res.redirect('http://localhost:8088');
    });

    app.get('/auth/check', (req, res) => {
        logger.info("Requesting: /auth/check");
        logger.info(req.isAuthenticated())
        if (!req.isAuthenticated()) {
            return res.status(401).json({
                message: 'No autorizado'
            });
        } else {
            return res.status(200).json({ user: req.user });
        }
    });

    app.get('/auth/logout', (req, res) => {
        logger.info("Requesting: /auth/logout");
        req.logout();
        return res.redirect('http://localhost:8088');
    });

};

Passport configuration is a SAML SSO:

const passport = require('passport');
const passportSaml = require('passport-saml');
const fs = require('fs');

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

passport.deserializeUser((user, done) => {
    done(null, user);
});

// SAML strategy for passport -- Single IPD
const strategy = new passportSaml.Strategy(
    {
    passReqToCallback: true,
        entryPoint: 'the-entrypoint-url',
        issuer: 'the-issuer',
        callbackUrl: 'the-callback-url',
        cert: fs.readFileSync('./secret/the-secret-cer.cer', 'utf-8'),
    privateKey:  fs.readFileSync('./secret/the-secret-key.key', 'utf-8')
    },
    (req, profile, done) => done(null, profile)
);

passport.use(strategy);

module.exports = passport;

It's okay. I have tested the request by the browser and can log in, keep the user's email in session, and then log out. But, when I call the server paths in the React interface app, I can't read the sessions. I suppose if I call the login paths with the browser it could read the session by React, but I don't.

axios({
  method: 'GET',
  url: 'the-server/auth/check',
  withCredentials: true
})
.then(response => {
  logger.info(response.data, 'SAML');
  if (response.data) {
    setLoading(false);
  }
  else {
    //AuthLogin();    
  }
})
.catch(error => {
  logger.error(error, 'SAML');
  //AuthLogin();
})

I really appreciate your help. Best regards my friends.

1 Answers

Try setting the express-session before your app.use(passport.initialize()) like this:

app.use(
        session({
            name: "cookie-name",
            resave: true,
            proxy: true,
            secret: 'secret',
            saveUninitialized: true,
            cookie: {
                secure: true,
                sameSite: "none"
            }
        })
    );
    app.use(passport.initialize())
    app.use(passport.session());

browsers are now blocking cookies that don't have samesite configured, as the default value is "lax" it enforces the execution of cookies in same domain, so, you have to configure the samesite to 'none' to allow executing cookies from cross sites domains.

sameSite to "none" to allow cross site

secure to true

proxy to allow the "X-Forwarded-Proto" header.

more info: https://www.npmjs.com/package/express-session

Related