Session Lost using nodejs express, cors, express-session - node.js

Working on a backend using nodejs, express, express-session, cors & cookie-parser to communicate with a react app that use axios to send http request, and using mariadb for the database.
Using both of them on localhost (3000 front end, 3001 backend) work fine, session is correctly saved and can be retrieved / used (I just pass the user data).
When deploying the backend on either local network or an EC2 instance from aws, the req.session return only the parameters set on "cookies" in the app.use(session({}) when called after being set on the login.
app.js:
const express = require('express');
const cors = require('cors');
const session = require('express-session');
const pool = require('./config/database');
const cookieParser = require('cookie-parser');
const app = express();
app.use(express.json());
app.use(cors(
{
credentials: true,
origin: true,
}
));
app.set('trust proxy', 1)
app.use(cookieParser());
app.use(session({
secret: 'cat on keyboard',
saveUninitialized: false,
resave: false,
cookie: { httpOnly: false, maxAge: 1000 * 60 * 60 * 24 }
}));
The req.session set
getAccountIdByEmail: async (req, res) => {
// connect & retrieve user from credentials //
req.session.logged_user = user[0][0];
return res.status(200).json({ success: user[0][0] })
};
The axios call from react app:
const fetchData = () => {
if (adress.charAt(0) == '/') {
adress = endpoint + adress;
}
axios({
method: method,
url: adress,
data: content,
withCredentials: true
})
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
At first i thougt it came from Nginx on Aws EC2, but it does the same calling directly on port 3001 of the instance, then i had the same issue on a local network.
I've tried also to use a store (express-session-mariadb-store or express-mysql-session), without success.
I think it might be tied to cors or headers, but couldn't pinpoint what doesn't work.

I noticed on express-session-npm
there is a disclaimer saying it is only for development and will have memory leaks if deployed in production

Related

Unable to access passport user in socket.io when using cors

I am creating a react app and I was adding functionality of registering users.
Everything was successful but I am unable to access Passport User property in socket I used the same code given in socket.io example
const session = require("express-session");
const passport = require("passport");
io.use(wrap(session({ secret: "cats" })));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error("unauthorized"))
}
});
This example works fine if domain is same but when I use CORS I am unable to access the passport property in session.
my react app domain is localhost:3000 and socket server domain is localhost:5000
Assuming that you are using same protocol and same domain but different ports it should still work fine if you setup your client and server with cors flags, e.g
// server-side
const io = new Server(httpServer, {
cors: {
origin: "https://example.com",
allowedHeaders: ["my-custom-header"],
credentials: true
}
});
// client-side
import { io } from "socket.io-client";
const socket = io("https://api.example.com", {
withCredentials: true,
extraHeaders: {
"my-custom-header": "abcd"
}
});
The sample above was taken from socket.io docs: https://socket.io/docs/v4/handling-cors/
However, the above configuration will work only if client/server are sharing the same top level domain and same protocol. e.g. client: https://example.com, server: https://server.example.com
I spent some time to figure out myself why:
client: http://127.0.0.1:3000 does not work with server: https://127.0.0.1:8000, notice the protocol difference.
With cors configurations in place, it works fine if I use http://127.0.0.1:8000 for server.
PS: If you need to use different top domains, be aware of SameSite policy that might be in place for your browser: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
This policy might restrict your cookies to be sent to server.
so... if different protocol or domains, you should make sure that you session cookie has SameSite flag set as 'none', via:
const session = require('express-session');
...
// Session setup
const sessionConfig = {
secret: 'secret', // Session secret
resave: false, //don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
cookie: {
sameSite: 'none',
secure: true,
}
}
const sessionMiddleware = session(sessionConfig);
app.use(sessionMiddleware);
...
io.use(wrap(sessionMiddleware));
both sameSite and secure properties are needed if you are playing with https:// protocol

Difficulty setting Cookie when using an ngrok tunnel (Express server on Node.js, React app frontend)

As outlined in the title, I am having difficulty setting a http cookie to be used for auth purposes when tunnelling using ngrok.
The following code works fine (obviously with the relevant endpoints specified) when i am running a query from from localhost to a localhost endpoint in my dev environment but breaks down as soon as i start to query the ngrok tunnel endpoint.
Frontend api query (simplified as part of larger application)
function fetchRequest (path, options) {
const endpoint = 'http://xxx.ngrok.io'; // the ngrok tunnel endpoint
return fetch(endpoint + path, options)
.then(res => {
return res.json();
})
.catch((err) => {
console.log('Error:', err);
});
}
function postRequest (url, body, credentials='include') {
return fetchRequest(`${url}`, {
method: 'POST',
withCredentials: true,
credentials: credentials,
headers: {'Content-Type': 'application/json', Accept: 'application.json'},
body: JSON.stringify(body)
});
}
// data to be passed to backend for authentication
let data = {pin: pin, username : username};
postRequest('/',data)
Express server on Node.js with ngrok tunnel (app.js)
const express = require('express')
const session = require('express-session')
const cors = require('cors')
const router = require('./router');
const tunnel = require('./ngrok')
const app = express()
const port = process.env.PORT || 4001;
app.use(cors({
origin: 'http://localhost:3000'
credentials: true,
}))
app.use(express.json());
const expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
expires: expiryDate
// sameSite: 'none'
// secure: true
}
}))
app.use(router)
let useNGROK = true;
if (useNGROK) {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
tunnel.createHTTPtunnel().then((url) => {
console.log(`New tunnel created with endpoint: ${url}`)
});
} else {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
}
Ngrok configuration (ngrok.js)
const ngrok = require('ngrok');
const find = require('find-process');
const port = process.env.PORT || '3000';
const tunnel = {
createHTTPtunnel: async function () {
const list = await find('name', 'ngrok');
if (list.length > 0) {
let api = ngrok.getApi();
if (api == null) {
this.kill_existing_tunnel();
} else {
let open_tunnels = await ngrok.getApi().listTunnels();
return open_tunnels.tunnels[0].public_url;
}
}
let ngrok_config = {
proto: 'http',
bind_tls: false,
name: process.env.NGROK_NAME,
hostname: process.env.NGROK_CUSTOM_DOMAIN,
// host_header: 'rewrite',
authtoken: '',
region: 'eu',
};
return ngrok.connect({ ...ngrok_config, addr: port });
},
kill_existing_tunnel: async () => {
const list = await find('name', 'ngrok');
list.forEach((p) => {
try {
process.kill(p.pid);
console.log(`Killed process: ${p.name} before creating ngrok tunnel`);
} catch (e) {
console.log(e);
}
});
}
}
module.exports = tunnel;
** router & controller (router.js & controller.js respectively) **
*router.js*
const router = require('express').Router();
const example = require('./controller')
router.post('/', example.authenticate);
module.exports = router;
*controller.js*
async function authenticate (req, res) {
try {
res.send(JSON.stringify('trying to send cookie'))
} catch (e) {
console.log('Error', e)
res.sendStatus(500)
}
}
module.exports = {
authenticate
};
The following information is provided when inspecting the Set-Cookie response header in the network requests:
This Set-Cookie header didn’t specify a “SameSite” attribute and was defaulted to “SameSite=Lax” and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with “SameSite=None” to enable cross site usage.
Attempted fix 1//
If I add the following options to the cookie {sameSite: ‘none’, secure:true}, amend the ngrok config to set {bind_tls: true} and run https on my front end (using a custom SSL certificate as per the create react app documentation), and query the https tunnel, then no cookie is received in the response from the server at all (request is sent and response 200 is received but with no cookie).
Attempted fix 2//
I also tried to change the host_header option to rewrite in the ngrok config (to mirror a response from localhost rather than from ngrok) and this did not work.
Any help would be much appreciated as I have little experience and I am stuck!

getting CORS and network error while calling axios post request using Vue js

I am getting error while calling axios post request. But it works properly on postman.
The code I used for calling the request is
methods : {
displayData(){
var config = {
method: 'post',
url: 'http://localhost:5000/api/request/displayRequest',
headers: {
'Content-Type': 'application/json'
},
data : JSON.parse(JSON.stringify(this.user._id))
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
},
async mounted(){
this.displayData()
}
I have already implemented CORS on the back-end in server.js
// Cors Middleware
const cors = require('cors');
app.use(cors());
app.options("*", cors());
app.use(
cors({
origin: (origin, callback) => callback(null, true), // you can control it based on condition.
credentials: true, // if using cookie sessions.
})
);
in your backend use this :
npm i cors
and in your express backend entrypoint:
const cors = require("cors");
app.use(cors());
app.options("*", cors());
You are running your front-end on localhost and using some port. Also, your back-end is running on localhost, port 5000. But your front-end application can not access any other port due to CORS policy. You can solve this problem in the back-end if you are using Node JS.
Install cors by the following command:
npm i cors
Then on your server file, change your app by
app.use(cors());
N.B. If you used React js, you could use http-proxy-middleware. Just create a file inside the src directory named "setupProxy.js". and add the following lines.
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:5000/",
})
);
};
Don't forget to change the port in this file into the port of your server.

Koa.js: ctx.setcookie() fails to set cookie

In my server.js code below I am setting up a middleware that should pass through Shopify OAuth and then redirect to the / route.
The '/' route, and its redirect url, are loaded in an iframe inside the shopify Admin area. I do see the page that / redirects to. But no cookies are present.
Related to the cookie settings, I am accessing this route in a web browser and on a secure https connection.
I am using Google Chrome Version 79.0.3945.88 (Official Build) (64-bit). I'm also using EditThisCookie browser extension to see the cookies that are present for the domain.
Can anyone tell why the cookies I am trying to set in server.js are failing to set?
import "isomorphic-fetch";
require("dotenv").config();
import Koa from "koa";
import Router from "koa-router";
import session from "koa-session";
import authorizeForShopify, {verifyRequest} from "#shopify/koa-shopify-auth";
const koa = new Koa();
const router = new Router();
const {SHOPIFY_BUYUSED_API_KEY, SHOPIFY_BUYUSED_API_SECRET, SHOPIFY_BUYUSED_SCOPES} = process.env;
koa.keys = [SHOPIFY_BUYUSED_API_SECRET];
koa.use(session({secure: true, sameSite: "none"}, koa));
////// Shopify OAuth //////
koa.use(authorizeForShopify({
apiKey : SHOPIFY_BUYUSED_API_KEY
, secret : SHOPIFY_BUYUSED_API_SECRET
, scopes : SHOPIFY_BUYUSED_SCOPES.split(",")
, afterAuth(ctx: Koa.Context): void {
console.log(`=====inside afterAuth()=====`); // I don't see this log statement
const {shop, accessToken} = ctx.session;
console.log({ // also I do not see this one
message : "from inside afterAuth()"
, shop
, accessToken
});
// cookie setting
const cookieOptions = {
httpOnly: true,
secure: true,
signed: true,
overwrite: true
};
// neither cookie is present in EditThisCookie
ctx.cookie.set("buyUsed_shopName", shop, cookieOptions);
ctx.cookie.set("buyUsed_generalToken", accessToken, cookieOptions);
ctx.redirect("/");
}
}));
////// Routing //////
router.get('/', async ctx => {
// ctx.body = "Koa server running, '/' route triggered"
ctx.redirect("https://storage.cloud.google.com/buy_used/consoleLog.js");
});
koa.use(verifyRequest());
koa.use(router.routes())
.use(router.allowedMethods());
const port: number = Number(process.env.PORT) || 8080;
koa.listen(port, undefined, undefined, () => console.log(`=====Koa listening on port ${port.toString()}=====`));
In the case of Koa, the methods to work with cookies are ctx.cookies.get and ctx.cookies.set. Thus, the lines should be changed to:
// neither cookie is present in EditThisCookie
ctx.cookies.set("buyUsed_shopName", shop, cookieOptions);
ctx.cookies.set("buyUsed_generalToken", accessToken, cookieOptions);
It works when setting, "secureProxy: true"
ctx.cookies.set('jwt', token, { httpOnly: true, secure: true, sameSite: "none", secureProxy: true });

Every time React sends request to Express, a new session is generated

I use React as a client to send request to Express with proxy and express-session set up. But every time React makes a request to Express server, a new session is created. So I checked Express alone by manually accessing to the same api url and it keep using the same session each time I refresh the page.
Project structure:
project-folder
- client // React client with proxy set up
+ src
+ package.json
+ ...
- server.js
- package.json
Inside server.js:
const session = require('express-session');
let sessionConf = {
name: 'aoid',
secret: 'stackoverflow',
resave: true,
saveUninitialized: true,
rolling: true,
cookie: {
httpOnly: false,
secure: false,
maxAge: 2000000
}
};
app.use(session(sessionConf));
app.get('/api/prod', (req, res, next) => {
let sessionId = req.sessionID; // is generated each time React client send request, works fine with api alone!
console.log(sessionId);
if (!sessionId) return res.status(401).send('Unauthorized Error');
res.status(200).send({ data });
});
Here is how React client send its request to Express:
let loadItems = async () => {
const response = await fetch('/api/prod');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
}
I think the problem comes from the misconfiguration between React and Express. Did anyone have this problem before?
fetch does not send cookie by default, you need to set it explicitly:
fetch(url, {
method: 'GET',
credentials: 'include',
// ...
})

Resources