Replacing nginx as a reverse-proxy with express - express

I would like to replace nginx, serving as a reverse proxy, with an node js express application so I can set-up the rules dynamically and have better logging and testing possibilities.
My nginx set-up looks like:
http {
include mime.types;
default_type application/octet-stream;
sendfile off;
keepalive_timeout 65;
gzip on;
server {
listen 8023;
server_name localhost;
sendfile off;
location / {
proxy_pass https://main.app.url.com/;
}
location /module1/v1/ {
client_max_body_size 30000M;
client_body_buffer_size 200000k;
# local backend of module 1
proxy_pass http://localhost:8080/module1/v1/;
}
location /module1/v1/web/ {
# local front end files for module 1
alias /some/local/path/build/dist;
}
location /module2/v1/web/ {
# local front end files for module 1
alias /some/other/local/path/build/dist;
}
}
}
I tried to use the express-http-proxy middleware, but I am struggling with applying the above rules to it. First, I do not fully understand the difference between proxy_pass and alias directives.
Second I tried following:
const express = require('express');
const app = express();
const proxy = require('express-http-proxy')
const path = '/some/local/path/build/dist';
app.all('/module1/v1/web/', proxy(path, {
proxyReqPathResolver: (req) => {
return '';
}
})
);
};
I got an error:
TypeError: Cannot read property 'request' of undefined
at /Users/user1/Dev/local-dev-runner/node_modules/express-http-proxy/app/steps/sendProxyRequest.js:13:29
at new Promise (<anonymous>)
at sendProxyRequest (/Users/user1/Dev/local-dev-runner/node_modules/express-http-proxy/app/steps/sendProxyRequest.js:11:10)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
Although cont path = 'http://www.google.com' returned an valid response.

So this is what I came with:
The trick is to serve local files and to proxy web requests. Apparently, nginx can automatically recognize whether the requests URI is an local or remote path.
I decided to use the nmp packages is-url and express-http-proxy so, the working solution looks like:
const express = require('express');
const app = express();
const isUrl = require('is-url');
const proxy = require('express-http-proxy');
...
/**
* Function adding new redirects to the current server instance. If the target URL is an URL the request will be
* handeled with the express-http-proxy middleware. If the target URL is some local directory the content will be serverd using the express-static middleware.
* #param matchedPath The path (endpoint) for which the redireciton should be configured.
* #param targetUrl The target URL or directory for the redirection.
*/
const setUpRedirect = (matchedPath, targetUrl, name) => {
// Use proxy for Urls
if (isUrl(targetUrl)) {
app.use(matchedPath,
proxy(targetUrl,
{
memoizeHost: false,
limit: '50mb',
proxyReqPathResolver: function(req) {
// I do not have (yet) any clue why I had to do this but it fixed the behavior
return req.originalUrl;
},
}),
)
}
else { // When targetUrl is directory serve static files from there
app.use(matchedPath,
express.static(targetUrl),
);
};
};
...
setUpRedirect('/module1/v1/web/:major([0-9]+).:minor([0-9]+).:bugfix([0-9]+)', '/some/local/path/build/dist';)
setUpRedirect('/module2/v1/web/:major([0-9]+).:minor([0-9]+).:bugfix([0-9]+)', 'http://some/remote/url/';)
server = app.listen(8080);

Related

How does this code enable the redirection flow?

I have a frontend www.myfrontend.tech and a backend www.mybackend.io. My previous developer managed to realize third-party authentications (like Microsoft) with PassportJS. But when looking at his code, I could not understand the flow and how the code enables this. Here is the code:
In the .conf of nginx of www.myfrontend.tech:
upstream mybackend {
server www.mybackend.io:443;
}
server {
location ~ /socialLoginSuccess {
rewrite ^ '/#/socialLoginSuccess' redirect;
}
location ~ /auth/(.*) {
proxy_pass https://mybackend/front/auth/$1?$query_string;
proxy_set_header Host www.myfrontend.tech;
}
... ...
}
In frontend/src/router.tsx of www.myfrontend.tech in ReactJS:
function RouterConfig({ history }: any) {
return (
<ConnectedRouter history={history}>
<Layout>
<Switch>
<Route path="/socialLoginSuccess">
<SocialLoginSuccess />
</Route>
... ...
In app.js of www.mybackend.io in NodeJS+ExpressJS+PassportJS:
var _front = require('./routes/front');
app.use('/front', _front);
In routes/front.js of www.mybackend.io:
router.get('/auth/microsoft', passport.authenticate('microsoft', { scope: ['User.Read'] }));
router.get('/auth/microsoft/callback', passport.authenticate('microsoft', {
successRedirect: '/auth/signinSuccess',
failureRedirect: '/auth/signinFailure',
failureFlash: true
}))
router.get('/auth/signinSuccess', function (req, res, next) {
res.redirect("/socialLoginSuccess");
})
In passport.module.js:
var Microsoft = (function() {
function Microsoft() {}
Microsoft.prototype.enable = function() {
var MicrosoftStrategy = require("passport-microsoft").Strategy;
passport.use(
"microsoft",
new MicrosoftStrategy(
{
clientID: keys.MICROSOFT_CLIENT_ID,
clientSecret: keys.MICROSOFT_CLIENT_SECRET,
callbackURL: `/auth/microsoft/callback`
},
this.loginSuccessfully
)
);
};
return Microsoft;
})();
During tests of this authentication, I can see in the address bar www.myfrontend.tech/auth/google and www.myfrontend.tech/#/socialLoginSuccess after signing in.
My question is, for example, where successRedirect: '/auth/signinSuccess' goes (backend or frontend)? and where does res.redirect("/socialLoginSuccess") go? How was it enabled by the code?
The flow:
User go to www.myfrontend.tech/auth/microsoft (forwarded to https://mybackend.io/front/auth/microsoft on the backend, since it matches location ~ /auth/(.*) on your nginx configuration)
It will redirect to Microsoft auth page
After user clicked authorize, it will redirected to the callback URL on your Microsoft Oauth App, which in your case is www.frontend.tech/auth/microsoft/callback (forwarded to https://mybackend.io/front/auth/microsoft/callback on the backend, since it matches location ~ /auth/(.*) on your nginx configuration)
On that callback, passport middleware checks if the authorization succeed or not, if it is, it will redirect to successRedirect URI, which is www.frontend.tech/auth/signinSuccess (forwarded to https://mybackend.io/front/auth/signinSuccess on the backend, since it matches location ~ /auth/(.*) on your nginx configuration)
Express app handle auth/signinSuccess success call, and redirects to /socialLoginSuccess, since /socialLoginSuccess location matches location ~ /socialLoginSuccess on your Nginx configuration (not location ~ /auth/(.*) therefor there's no proxy pass), it will be redirected to www.frontend.tech/#/socialLoginSuccess (by the rewrite ^ '/#/socialLoginSuccess' redirect;)
Since it's redirected to www.frontend.tech/#/socialLoginSuccess, it's now being handled by React router
And to answer your questions:
successRedirect will go to your front end, so it will be www.myfrontend.tech/auth/signinSuccess, but since you have proxy configuration on Nginx, and it matches location ~ /auth/(.*), it's being forwarded to www.mybackend.io/front/auth/signinSuccess (read more about reverse proxy)
res.redirect("/socialLoginSuccess") will redirect you to www.frontend.tech/#/socialLoginSuccess, the reason is mentioned on the 5th flow above

Express - Finding subdomain(s)

I have an Express API that's being called from a VueJS app. The app is located at a domain similar to dev.example.com. Part of my API call checks for subdomains using the built in req.subdomains. In this case it should return an array containing "dev", however, it's returning an empty array. Both the VueJS app and Express API are running on an AWS EC2 instance.
EC2 Nginx File
server {
listen 80 default_server;
server_name _;
#Vue App & Frontend Files
location / {
root /var/www/frontend-app/dist;
try_files $uri /index.html;
}
#Node API Reverse Proxy
location /api/ {
proxy_pass http://localhost:3000/;
proxy_set_header Host $host; //Fix: Passes down the requesting host information
}
}
Frontend API Call
submit() {
axios.post('/api/users/login', {
email:'test#example.com',
password: 'password'
})
}
Express API Sample Route
router.post('/users/login', async(req, res) => {
*Login code omitted*
res.send(req.subdomains); //Returns [], expected result ['dev']
})
I haven't been able to find any solution and was hoping someone would have an idea. Thanks!

Vue.js app served on nginx, using Axios/Express, loses HTTPS and becomes HTTP after login page

My Vue.js app switches from HTTPS to HTTP after the user logs in, and I'm not sure why. The Vue.js front-end is served using nginx, and uses Axios to access an Express back-end API that queries a local MongoDB server for login and a remote SQL Server for data. If someone could provide any guidance, that would be much appreciated, as this is for a work project.
I'm not sure if this could be a clue, but after logging into a session in the app, if I open a new tab and manually enter a URL for a view that doesn't access the Express back-end (https://subdomain.website.com/view_that_doesnt_access_express_api), the site will be secure under HTTPS, but if I do the same with a view that does access the Express back-end (https://subdomain.website.com/view_that_accesses_express_api), the site will be unsecure and the HTTPS in the location bar is shown crossed out in red.
nginx is configured with SSL and redirects 80 to 443 as follows (/etc/nginx/sites-available/project):
server {
listen 80;
server_name subdomain.website.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name _;
ssl_certificate /path/to/my/cert.crt;
ssl_certificate_key /path/to/my/key.rsa;
root /path/to/my/dist;
index index.html
access_log /var/log/nginx/site.access.log;
error_log /var/log/nginx/site.error.log;
location / {
try_files $uri /index.html =404;
}
}
The Express server is also configured to be HTTPS (app.js) with the same certification files used by nginx:
var httpscert = fs.readFile('cert.crt');
var httpskey = fs.readFile('key.rsa);
var options = {
key: httpskey,
cert: httpscert
};
var server = https.createServer(options, app);
server.listen(3000, function () {
console.log("Express HTTPS running on 3000");
})
Axios (Service.js):
import Api from '#/services/Api'
export default {
loginUser (email, password) {
return Api().post('login', {
email: email,
password: password
})
}
}
Axios (Api.js):
import axios from 'axios'
export default() => {
return axios.create({
baseURL: 'https://192.168.10.117:3000'
})
}
This is my first question on StackOverflow, so please let me know if I'm not asking this question correctly.
Thank you.
You said:
but if I do the same with a view that does access the Express back-end (https://subdomain.website.com/view_that_accesses_express_api), the site will be unsecure and the HTTPS in the location bar is shown crossed out in red.
That does not mean your setup is wrong, it just means you have on that page an asset that is served via HTTP, instead of HTTPS. And that's why the lock is crossed.
You should inspect your page source and check for any scripts or styles that are loaded via HTTP. Change those to be HTTPS and you should be good to go.

Express 4 middleware not calling routes on DigitalOcean server

I'm messing around with creating an API with io.js and Express 4, and I have no idea why this isn't working. I am currently running the program on a DigitalOcean droplet (Ubuntu 14.04) and it is not calling the next() method never gets called/executed. The program is being routed to by a reverse proxy with nginx at https://<redacted>/asdf.
var express = require('express');
var app = express();
var port = process.env.PORT || 3000;
var router = express.Router();
router.use(function(req, res, next) {
console.log("Request received.");
next();
});
router.post('/login', function(req, res) {
console.log('Route /login accessed');
});
app.use('/', router);
app.listen(port);
console.log("Server started on port " + port + ".");
When I run the program on my droplet and send a POST request to https://<redacted>/asdf/login, the console prints out "Request received" but does not print out "Route /login accessed". The odd thing is that this works perfectly fine on my local machine when a post request is sent to http://localhost:3000/login. Is there something I am doing wrong or is this a known thing that I am not aware of?
Express uses the request's url property when mapping routes, and I suspect that your nginx reverse proxy isn't removing the /asdf root from it. If so, your url's path would be /asdf/login which would explain why your /login post handler isn't being invoked. To test this hypothesis you could try adding the reverse proxy root to your use like this:
app.use('/asdf', router);
If this is the case, to fix this problem, you can configure nginx to rewrite the url for you like this
location /asdf {
rewrite /asdf(.*) $1 break;
proxy_pass http://localhost:3200;
proxy_redirect off;
proxy_set_header Host $host;
}
More details:
https://serverfault.com/questions/379675/nginx-reverse-proxy-url-rewrite
http://wiki.nginx.org/HttpRewriteModule#rewrite

Node.js proxy for ws.audioscrobbler.com responds with 301 to www.last.fm

I’m trying to use Node.js to set up a proxy to Last.fm’s webservices. The problem is that every request to ws.audioscrobbler.com gets rewritten to www.last.fm. So for example $ curl http://localhost:8000/_api/test123 sends a 301 Moved Permanently to http://www.last.fm/test123.
var express = require('express'),
httpProxy = require('http-proxy');
// proxy server
var lastfmProxy = httpProxy.createServer(80, 'ws.audioscrobbler.com');
// target server
var app = express.createServer();
app.configure(function() {
app.use('/_api', lastfmProxy);
});
app.listen(8000);
At the same time $ curl http://ws.audioscrobbler.com/test123 returns a regular 404 Not Found. I’m not exactly sure what I’m missing here, or if I’m approaching this completely the wrong way.
The reason you get a 301 Moved Permanently is that ws.audioscrobbler.com gets an HTTP request with the hostname "localhost".
One solution is to let the proxy rewrite the hostname to "ws.audioscrobbler.com" before passing it on to the remote server:
var httpProxy = require('http-proxy');
var lastfmProxy = httpProxy.createServer(function (req, res, proxy) {
req.headers.host = 'ws.audioscrobbler.com';
proxy.proxyRequest(req, res, {
host: 'ws.audioscrobbler.com',
port: 80,
});
}).listen(8000);