In my last assignment we have a proxy which handles authentication. Before its creation we had to think about a way to still work locally, while actually using that same proxy.
So this started simple because the first thing what came to my mind was `http-proxy-middleware`, from my former colleague Steven. Who also won our bet on “number of downloads of an own library” by the way (yes sometimes i lose a lot of bets during an assignment).
The first Prototype was created instantly. As always the first version was done in an hour, but then the real problems started. We have to alter the Response header of the proxied response asynchronously too.
Looking at the recipes of the http-proxy-middleware, then searched in its issues and saw some people having the same problem with no solution found. It seemed not possible, at least no movement.
So what will a bad developer like me do? Exactly, try to make it work. The second version eventually worked, but not acceptable. It was good for the feeling it can be done and others could proceed on their part. So let’s take this challenge one more time. How to do async header transformation just before sending the Response?
We are using Express for a simple middleware chain:
app.use("/", entryMiddleware, proxyMiddleware);
Before entering the proxyMiddleware I needed to do an async call to our real authentication proxy for its headers and add it to the Request. Also a possible ‘set-cookie’ with its Domain and SameSite get stripped to make that working locally as well. For ‘set-cookie’ we had to make sure if we get an Array or not. But simple to solve.
res.setHeader('set-cookie', Array.isArray(strippedCookie) ? strippedCookie : strippedCookie.split(','));
This is all done in our ‘entryMiddleware’.
Later on we also added more middleware. For example a correlation identity, which uses the os username to make searching logs more personal and easier.
Now the proxy middleware itself. The impossible made possible in a single line :)
const proxyMiddleware = createProxyMiddleware({
target: "http://url-here",
changeOrigin: true,
selfHandleResponse: true,
onProxyReq: (proxyReq, req, res) => {
proxyReq.setHeader("mpth-1", "da");
},
onProxyRes: async (proxyRes, req, res) => { const da = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ wei: "wei" });
}, 200);
}); res.setHeader("mpth-2", da.wei); proxyRes.pipe(res); }
});
Now just before we pass back the response, we can also do asynchronous stuff , which in my case was also calling for headers when state changed. And then pipe the response itself (`proxyRes.pipe(res)`). Done.
The shared recipe can be found here https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/async-response.md, so this article or maybe the recipe will help others.
After creating the working reverse proxy locally (to emulate our real authentication proxy) it’s time to zip it and create a shared npm library with configuration so everyone in our organisation can use it. Now the simple task starts.
For a runnable shared library all we need to add in its ‘package.json’ is its location:
"name": "@mpth/proxy",
"description": "reverse proxy your application",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"proxy-me": "bin/run.js"
},
So when installed `@mpth/proxy` the `proxy-me` command will start the express proxy. For configuration we allowed the consumer to have settings inside a `proxy-settings.json` which the library will pickup from its `process.cwd()`.
This config is then used to re-route things when applicable (for example no need for auth on static assets), and all options the `http-proxy-middleware` library supports. So rewriting paths, pointing some entries to colleague computer etc etc. The local proxy itself is starting an express sever on http and https with configurable ports and even option to have your own local `mkcert` certificates.
In the end the shared private npm library ‘proxy’ is highly configurable and works with any frontend framework our consumers can possible use. What seemed not doable, turned out to be rather simple. Hopefully it will inspire or at least help other developers to simply not give up and not accepting crappy solutions.
[nginx] initializing proxy
[nginx] 📦 Own certificates are being used
[nginx] Enable proxy route from /gql to https://graphql-proxy
[nginx] [HPM] Proxy created: / -> https://graphql-proxy
[nginx] Enable proxy route from /__webpack_hmr to http://localhost:3001
[nginx] [HPM] Proxy created: / -> http://localhost:3001
[nginx] Enable proxy route from /kookschrift/assets to http://localhost:3001
[nginx] [HPM] Proxy created: / -> http://localhost:3001
[nginx] Enable proxy route from /kookschrift to http://localhost:3001
[nginx] [HPM] Proxy created: / -> http://localhost:3001
[nginx] Enable proxy route from / to https://domain
[nginx] [HPM] Proxy created: / -> https://domain
[nginx] ⚡ http proxy started on http://localhost:3000
[nginx] this proxy is for local development only!
[nginx] 🚀 https proxy started on https://localhost:8444
[nginx] this proxy is for local development only!
Thank you Thijs Kaper for working on the real authentication proxy (with Stanislau and Aryadna) and keep pushing and helping me to make it work correctly.