Home > database >  Express session doesn't persist if the client is on a different domain
Express session doesn't persist if the client is on a different domain

Time:01-14

tl:dr;

A Node (express) server is hosted on Heroku, and the UI is hosted on Netlify. When the UI makes a REST API call to the server, the session doesn't persist (but it persists if I ran both locally. localhost:5000 on the server, localhost:3000 on UI. The UI is proxying requests with package.json).

Code snippets

session.ts

export const sessionConfig = {
  secret: process.env.SESSION_KEY,
  store: new RedisStore({ client: redisClient }),
  resave: true,
  saveUninitialized: true,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax',
  },
};

server.ts

const app = express();

app.use(express.json());
app.use(cookieParser());

app.set('trust proxy', 1);
app.use(session(sessionConfig)); // This sessionConfig comes from the file above

app.use(cors({
  credentials: true,
  origin: process.env.CLIENT_URL,
}));

I googled something like express session not persist when cross domain request. Then, I saw threads like this and this. It seems that app.set('trust proxy', 1) will make sure that session data will be persisted for cross-domain requests. Apparently, in my case, something is still missing.

Does anyone see what I'm doing wrong? Any advice will be appreciated!

PS:

I'm using sessions for captcha tests, which looks like...

captch.ts

CaptchaRouter.get('/api/captcha', async (req: Request, res: Response) => {
  const captcha = CaptchaService.createCaptcha();
  req.session.captchaText = captcha.text;
  res.send(captcha.data);
});

CaptchaRouter.post('/api/captcha', async (req: Request, res: Response) => {
    if (req.session.captchaText !== req.body.captchaText) {
      throw new BadRequestError('Wrong code was provided');
    }

    // The client sent the correct captcha
  },
);

Another PS: Here's how the response heders look like:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://example.netlify.app
Connection: keep-alive
Content-Length: 46
Content-Type: application/json; charset=utf-8
Date: Sun, 09 Jan 2022 00:00:00 GMT
Etag: W/"2e-cds5jiaerjikllkslaxmalmird"
Server: Cowboy
Set-Cookie: connect.sid=s%3ramdon-string-here; Path=/; Expires=Sun, 09 Jan 2022 00:00:00 GMT; HttpOnly; Secure; SameSite=None
Vary: Origin
Via: 1.1 vegur
X-Powered-By: Express

CodePudding user response:

The cause was that the client (hosted on Netlify) wasn't proxying API requests.

The solution was:

  1. add _redirects under public of the client
/api/*  https://server.herokuapp.com/api/:splat  200

/*  /index.html  200

  1. make sure that API requests from the client will begin with the root URL
return axios({ method: 'POST', url: '/api/example', headers: defaultHeaders });

For future reference, here's my session config

const sessionConfig = {
  secret: process.env.SESSION_KEY || 'This fallback string is necessary for Typescript',
  store: new RedisStore({ client: redisClient }),
  resave: false,
  saveUninitialized: true,
  cookie: {
    secure: process.env.NODE_ENV === 'production', // Prod is supposed to use https
    sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax', // must be 'none' to enable cross-site delivery
    httpOnly: true,
    maxAge: 1000 * 60
  } as { secure: boolean },
};

...and here's server.ts

const app = express();
const port = process.env.PORT || 5000;

app.use(express.json());

app.set('trust proxy', 1);
app.use(session(sessionConfig));

app.use(cors({
  credentials: true,
  origin: process.env.CLIENT_URL,
}));

(As @Matt Davis pointed out, cookieParser was unnecessary)

  •  Tags:  
  • Related