Recently I had to move JWT to cookies to used it between Docker microservises seamlessly. We run each container on separate url prefix with was tricker to figure out as cookie have to be there when url is changed. I couldn’t find any straightforward solution so I decided to write it as it might be useful for someone or even for future me.
I just assume you have working front-end and back-end containers and everything run fine. I won’t be explaining what cookies are as there’re plenty of better articles on that topic.
Basic setup
Let’s use simple Express server as example how to send cookies.
// index.js
const express = require("express")
const session = require("express-session");
const app = express()
app.use(
session({
secret: process.env.LOGIN_SERVER_SECRET,
saveUninitialized: true,
resave: true,
cookie: {
httpOnly: false,
secure: false,
},
}),
);
app.get("/cookie", (req, res) => {
const options = {
secure: false,
httpOnly: false,
domain: ".your.domain.com"
}
return res
.cookie("cookieName", "cookieValue", options)
.status(200)
.send("cookie sent")
})
app.listen(8080)
In this case whenever we send request to localhost:8080/cookie server responds with Set-Cookie header. That works fine when you type it directly in your browser or in app like Postman. Problem starts when you run you client on client.your.domain.com and server on server.your.domain.com. We start getting CORS issues.
Let’s see basic setup for out client app. I used create-react-app and just modified it by adding superagent (great library for requests) and sending request whenever I click on the link.
// App.js
import React from "react";
import superagent from "superagent";
import logo from "./logo.svg";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
onClick={() =>
superagent
.get("http://localhost:8080/cookie")
.then(response => {
console.log(response);
})
}
>
Get Cookie
</a>
</header>
</div>
);
}
export default App;
CORS
Since we sending request from different originator we get CORS issues. Simple solution to do that is to install cors package and add it as by simple example in their docs.
Again, simple cors with wildcard (*/*)
won’t work. We have to set up some custom config for cors and catch pre-flight OPTION check.
// index.js
const express = require("express")
const session = require("express-session");
const cors = require("cors")
const app = express()
app.use(
session({
secret: process.env.LOGIN_SERVER_SECRET,
saveUninitialized: true,
resave: true,
cookie: {
httpOnly: false,
secure: false,
},
}),
);
const corsOptions = {
origin: /\.your.domain\.com$/, // reqexp will match all prefixes
methods: "GET,HEAD,POST,PATCH,DELETE,OPTIONS",
credentials: true, // required to pass
allowedHeaders: "Content-Type, Authorization, X-Requested-With",
}
// intercept pre-flight check for all routes
app.options('*', cors(corsOptions))
// add cors middleware to route
app.get("/cookie", cors(corsOptions), (req, res) => {
const options = {
secure: false,
httpOnly: false,
domain: ".your.domain.com"
}
return res
.cookie("cookieName", "cookieValue", options)
.status(200)
.send("cookie sent")
})
app.listen(8080)
There is one more change on the front-end. Since our server now accepts request with credentials we have to send one to pass the cookie. It’s literally one line extra
// App.js
import React from "react";
import superagent from "superagent";
import logo from "./logo.svg";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
onClick={() =>
superagent
.get("http://localhost:8080/cookie")
.withCredentials() // it's simple as that
.then(response => {
console.log(response);
})
}
>
Get Cookie
</a>
</header>
</div>
);
}
export default App;
Secured cookies
As you might noticed i used unsecured cookies in above examples. Thet’s for dev/local purposes only. If you want to use it in production you have to take care about security. Secure cookies will work only on https so you have to take care of that as well. Good idea is to set cookies security dependent on NODE_ENV, so we don’t have to remember about it when working on dev and then deploy to prod.
// index.js
===
app.use(
session({
secret: process.env.LOGIN_SERVER_SECRET,
saveUninitialized: true,
resave: true,
cookie: {
httpOnly: true, // change both to true
secure: true,
},
}),
);
===
// dynamic change
const isCookieSecure =
process.env.NODE_ENV === "production" ? true : false;
// add cors middleware to route
app.get("/cookie", cors(corsOptions), (req, res) => {
const options = {
secure: isCookieSecure,
httpOnly: isCookieSecure,
domain: ".your.domain.com"
}
return res
.cookie("cookieName", "cookieValue", options)
.status(200)
.send("cookie sent")
})
That’s basically it. You can add as many apps and servers to your docker as you want and enjoy cookies everywhere. They’ll be passed automatically in the request and responses headers. Thanks you all for getting that far, hopefully that’s gonna be useful for someone :)
Read more about CORS, OPTIONS and cookies at MDN. Any questions or feedback just post a comment. Thanks 🙏
Top comments (1)
Hi!l I know this is an old post, but maybe I'll get a response. I've been learning React and Express apart in different courses, but It's being hard for me to put them together. How can you make the express-session cookie to appear on the react frontend client?