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

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 });

Related

React - httpOnly cookies not getting set from the express server

Vite + React: http://localhost:5173
Express: http://localhost:3000
Here is the code in the express server's login route:
const mycookie = cookie.serialize("jwt", refreshToken, {
httpOnly: true, // Set the HTTP-only flag
secure: true, // Set the secure flag
sameSite: "none",
path: "/", // Set the path of the cookie to '/'
maxAge: 3600, // Set the maximum age of the cookie to 1 hour
});
// Set the cookie in the response headers
res.setHeader("Set-Cookie", mycookie);
res.json({
accessToken,
refreshToken,
});
Here is my cors config:
const allowedOrigins = require("./allowedOrigins");
const corsOptions = {
crendials: true,
origin: function (origin, callback) {
console.log(allowedOrigins.indexOf(origin));
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
};
Here is my handleSubmit function in the frontend (gets called when the user clicks submit):
const handleSubmit = () => {
console.log("Login");
// axios.defaults.withCredentials = true;
axios.post("http://localhost:3000/api/v1/auth/login", { email, password });
};
Whenever the request is made to the server. The response header does contain the set-Header to set the jwt token but I am not able to see it in my applications tab under cookies in devtools. A pre-flight request also comes in which probably clears the cookie.
My networks tab:
The xhr request:
The OPTIONS request:
The Applications tab:
However, when I disable CORS in the browser, the cookie is getting set.
Networks tab (NOTICE: No PreFlight request)
Applications tab:
I tried working out different answers from stackoverflow answers like this, this, this, and many more along with a reddit post on a similar issue but nothing has worked.
P.S. : I already tried using credentials: true

Session Lost using nodejs express, cors, express-session

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

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

Node JS, express-session, remove cookie from client browser

I work with app, that already has its own infrastructure. The task is to prevent user login in several browser. Our application has single app architecture, so ideally user should work only in one browser tab. And I have a problem. I can’t remove cookie from client.
I. Briefly.
App settings:
Server: NodeJS
Port: 8083
Client: VueJS
Port: 8088
I use module express-session to initialize session mechanism on server side and send cookies to client. Client hasn’t set cookies.
II. Details:
Server’s root file is index.js
I do the following in it:
Plug in express module:
const express = require('express')
Plug in cors module:
const cors = require('cors')
Add cors settings:
app.use(cors({
origin: 'http://localhost:8088',
credentials: true
}))
Then I initialize session in user.js file and receive client’s connects:
Plug in express-session module:
const session = require('express-session')
Plug in routing by express.Router():
const router = express.Router()
Add session settings:
const EIGHT_HOURS = 1000 * 60 * 60 * 8
const {
SESS_NAME = 'sid',
SESS_LIFETIME = EIGHT_HOURS,
SESS_SECRET = 'test',
NODE_ENV = 'development'
} = process.env
const IN_PROD = NODE_ENV === 'production'
Initialize session:
router.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized: false,
secret: SESS_SECRET,
cookie: {
maxAge: SESS_LIFETIME,
sameSite: false,
// Must have HTTPS to work 'secret:true'
secure: IN_PROD
}
}))
Receive client queries by router.post()
So what I did:
I use req.session.destroy to remove session data and expect the browser logout user from certain browser and cookies clear.
req.session.destroy(err => {
if (err) {
return res.send({ error: 'Logout error' })
}
res.clearCookie(SESS_NAME, {path: '/'})
return res.send({ 'clearSession': 'success' })
})
Unfortunately nothing magic happens
I read different topics. For example, here (GitHub) offer the conclusion: use explicit cookie’s path indication in res.clearCookie method as shown above.
That didn’t work.
Wrote this setting {path: '/'} in cookies settings. Didn’t work too.
router.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized: false,
secret: SESS_SECRET,
cookie: {
path: '/',
maxAge: SESS_LIFETIME,
sameSite: false,
// Must have HTTPS to work 'secret:true'
secure: IN_PROD
}
}))
And as wrote in express-session documentation (NPM:express-session) this path is the default path for cookie storage.
Add req.session = null in req.session.destroy:
req.session.destroy(err => {
            if (err) {
                return res.send({ error: 'Logout error' })
            }
            req.session = null
            res.clearCookie(SESS_NAME, {path: '/'})
            return res.send({ 'clearSession': 'success' })
        })
That didn’t work
delete req.session doesn’t work too.
So, how can I resolve this problem? What should I do?
adding .send('cleared cookie') made my browser clear its cache of the named cookie.
const logOutRequest = (req, res) => {
req.session.destroy((err) => {
res.clearCookie("notcookie").send('cleared cookie');
});
};
Have you tried removing the exact cookie by setting it to null that is lets say that you are dealing with a cookie named Views you could remove the cookie using req.session.Views = null
Instead of doing this
req.session.destroy(err => {
if (err) {
return res.send({ error: 'Logout error' })
}
req.session = null
res.clearCookie(SESS_NAME, {path: '/'})
return res.send({ 'clearSession': 'success' })
})
you could do the name of your session cookie and set that to null that is
req.session.sid= null
This removes the cookie from client browser
req.session.destroy(err => {
res.clearCookie("session-cookie-name", { path: "/" });
});
The most important key is setting 'domain' in the clearCookie method to solve your issue. Expressjs will return the following in the http response header. However, it seems that on the browser or some browsers that I tested, it doesn't know which cookie belonging to which domain to clear, hence, the cookie remains there. You do not need to include the path when calling clearCookie
Set-Cookie: mycookie=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
You have to set domain like below:
req.session.destroy(err => {
res.clearCookie("session-cookie-name", { domain: 'your-domain' });
});
Then response header will become
Set-Cookie: mycookie=; Domain=your-domain; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
The browser will clear the cookie nicely!
this works for me
#Post('signout')
async signout(#Req() req: Request, #Res({ passthrough: true }) res: Response) {
const user = req.user
if (!user) return {}
await new Promise<void>((resolve, reject) => {
req.session.destroy((err) => {
if (err) {
reject(err)
} else {
res.clearCookie('ACCESS_TOKEN', {
domain: '.xxx.com'
})
res.clearCookie('REFRESH_TOKEN', {
domain: '.xxx.com'
})
res.clearCookie('connect.sid', {
domain: '.xxx.com'
})
resolve()
}
})
})
return {}
}

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