ProxyPassReverse doesn't rewrite Location (http header) - reverse-proxy

I have an apache installed in a frontend server (server1), which is as reverse proxy. I have another server (server2) with tomcat that is running a webapp.
I configured my reverse proxy (server1) like that:
ProxyPass /app1/ ajp://server2:8009/app1/
ProxyPassReverse /app1/ https://www.external_domain_name.com/
When I connect to:
https://www.external_domain_name.com/app1/
my web app is working properly. In some pages, the web app redirects me (302) to another page.
Then, I am redirected to :
https://server1_internal_ip/app1/foo_bar
When I look to the http headers, the response header contains:
Status code: 302
Location: https://server1_internal_ip/app1/foo_bar
So, my conclusion ProxyPass is working properly, but the ProxyPassReverse is not.
Can you help me please to understand what's going wrong?
Thanks

Actually ProxyPassReverse will replace the Location which your server returned.
Example 1 (Only URL Path)
Apache2 Setting
ProxyPass "/8080" "http://localhost:8080"
ProxyPassReverse "/8080/" "/"
Node.js Setting
const express = require("express");
const app = express()
app.get('/', (req, res) => {
res.json({a: 8080})
})
app.get("/hi", (req, res) => {
res.json({a: "8080hi"})
})
app.get("/redirect", (req, res) => {
res.redirect("/hi")
})
app.listen(8080)
Original Location is "Location: /hi".
New one is "Location: /8080/hi". (/ => /8080/)
That means Apache2 replaced the Location value with ProxyPassReverse setting.
Or you can use full FQDN to do it.
Example 2 (FQDN)
Apache2 Setting
ProxyPass "/8080" "http://localhost:8080"
ProxyPassReverse "/8080" "http://localhost:8080"
Node.js Setting
const express = require("express");
const app = express()
app.get('/', (req, res) => {
res.json({a: 8080})
})
app.get("/hi", (req, res) => {
res.json({a: "8080hi"})
})
app.get("/redirect", (req, res) => {
res.setHeader("Location", "http://localhost:8080/hi")
res.send(302)
})
app.listen(8080)
Apache2 will convert http://localhost:8080/hi to http://localhost/8080/hi.
(If my Apache2 is configured to 80 port.)

Set it to this instead
ProxyPassReverse /app1/ ajp://server2:8009/app1/
Seemed to work for me when I came across a similar problem.

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

Reverse Proxy Sending 127.0.0.1 as URL, instead of external site-url.com to oauth callback

I am transferring a site from the defunct OpenShift v2, to LightSail on AWS. I have the app up and running on LightSail at localhost:3333, forwarded externally. I am able to pull up the site using the site-url.com
However, when attempting to login to the app (using Passport Facebook). The callback url is getting set to 127.0.0.1, instead of the whitelisted (facebook dev) www.site-url.com
https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A3333%2Fauth%2Fwww.site-url.com%2Fauth%2Ffacebook%2Fcallback&scope=email&client_id=XXX
Relevant login code:
const appUrl = "www.site-url.com";
const callbackURL = appUrl + "/auth/facebook/callback";
passport.use(new FacebookStrategy({
clientID: clientID,
clientSecret: clientSecret,
callbackURL: callbackURL,
profileFields: ['id', 'displayName', 'email']
},
...
app.get('/auth/facebook',
passport.authenticate('facebook', { scope: ['email'] }));
app.get('/auth/facebook/callback',
passport.authenticate('facebook',{
successRedirect: appUrl + '/profile',
failureRedirect: appUrl + '/?login-failed'}
));
I added appUrl, in an attempt to fix it via server code. However, I have a feeling Apache would be better suited at fixing this.
I setup the Proxy, following these instructions, and tried all variations of 127.x/site-url.com
ProxyPass / http://127.0.0.1:3333/
# ProxyPass / http://www.site-url.com/
ProxyPassReverse / http://127.0.0.1:3333/
# ProxyPassReverse / http://www.site-url.com/
Anyone have any ideas?
Turning on PreserveHost solved the issue, Facebook is now receiving the correct callback url
PreserveHost:
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3333/
ProxyPassReverse / http://127.0.0.1:3333/
Apache config:
vim /opt/bitnami/apache2/conf/bitnami/bitnami-apps-prefix.conf
Append: Include "/home/bitnami/conf/httpd-app.conf
Start up the app using screen to avoid shutdown when SSH process is killed. Maybe try nodemon for resiliency
Thanks, #DusanBajic!

How can I make my nextjs with Express site work on ssl

We have a site running on Next.js and Express. This is on a cPanel server with Aapche and together with nginx serving as reverse proxy.
I need to have ssl on the site. But I am quite confuused with how the configurations should be.
My server.js :
const express = require('express')
const next = require('next')
const https = require('https');
const fs = require('fs');
//const forceSSL = require('express-force-ssl')
var ssl_options = {
key: fs.readFileSync('/home/myreactsite.key'),
cert: fs.readFileSync('/home/myreactsite.crt'),
};
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
const favicon = require('serve-favicon')
const path = require('path')
app.prepare()
.then(() => {
const server = express()
server.use(favicon(path.join(__dirname, 'static', 'images', 'favicon.ico')))
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(3007, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3007')
})
var httpsServer = https.createServer(ssl_options,server).listen('8445', (err) => {
if (err) throw err
console.log('> Ready on https://localhost:8445')
})
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})
Apache runs on 8080
Nginx runs on 80
Next.js runs on both 3007 and 8445(I prefer it for ssl)
My Apache config contains the following to hide the port 3007
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPass / http://myreactsite.com:3007/
The site works fine if I access it as http://myreactsite.com . But it fails when I access https://myreactsite.com though I can access https version by specifying the port number as https://myreactsite.com:8445
I want to make it work without specifying the https port.
How can I get my site to force all pages to https without specifying the port?
You probably want to use Apache for all the SSL handling and listen to the 443 port, then proxy to your 3007 port. Try this config:
<VirtualHost *:443>
ProxyPreserveHost On
ProxyRequests Off
ServerName myreactsite.com
ServerAlias myreactsite.com
ProxyPass / http://0.0.0.0:3007/
ProxyPassReverse / http://0.0.0.0:3007/
SSLEngine On
SSLProxyEngine On
SSLCertificateFile /home/myreactsite.crt
SSLCertificateKeyFile /home/myreactsite.key
</VirtualHost>
To redirect all HTTP traffic then:
<VirtualHost *:80>
ServerName myreactsite.com
Redirect / https://myreactsite.com/
</VirtualHost>
Based on #fabian comment, I am posting my working configurations if it helps someone...
Added the following lines in the 443 virtual host section for the site in apache.conf :
ProxyPreserveHost On
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPass / http://example.com:3000/
ProxyPassReverse / http://example.com:3000/
SSLProxyEngine On
#To redirect to https and www version
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]
Also, added the following line in the nginx vhost file for the site :
server {
...
...
#To redirect all http requests to https+www
return 301 https://www.example.com$request_uri;
...
...
}

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

basic app + express + mongodb + how to not use the port?

This is a basic app I have got that uses express for setting up routes and and also does some query on a mongo db
If I go to http://localhost:8080/ whatever is in the views/hello.html will be displayed in the browser.
If i go to http://localhost:8080/test 'This is a test Page' will be displayed in the browser.
My question is why do I have to specify the port 8080 in the address? Or put another way how do I display what i want at this address http://localhost/ without specifying the port?
I know I can change the port by changing the value of 8080 here
app.listen(8080);
basic app below:
var express = require('express'),
app = express(),
cons = require('consolidate'),
crypto = require('crypto'),
MongoClient = require('mongodb').MongoClient;
app.engine('html', cons.swig);
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
MongoClient.connect('mongodb://localhost:27017/m101', function(err, db) {
if(err) throw err;
//set up a route to go to the page http://localhost:8080/ to see 'This is a test Page'
app.get('/', function(req, res){
db.collection('hw1_3').findOne(function(err, doc) {
//do stuff here
return res.render('hello', { "name" : decrypted });
});
});
//set up a route to go to the page http://localhost/test to see 'This is a test Page'
app.get('/test', function(req, res){
return res.send('This is a test Page!!', 200);
});
app.listen(8080);
console.log('Express server started on port 8080');
});
The default port for http traffic is 80. If you bind to any port other than 80, you need to specify the port in the URL. app.listen(80) will take care of your problem.
On Unixy systems, root (administrator) access is required to bind to any port less than 1024, so you'll have to run your server like sudo node server.js to get port 80. You should bind to a higher port (like 8080) in this case while developing on your machine.
I'm pretty noob, but I'd say that localhost:8080 displays in place of www.somesite.com. I wouldn't get too wrapped up in the port number. If you deploy it to heroku or something you wont see it.