7

I have a flask application using nginx for a reverse proxy/ssl termination, but I'm running into trouble when using url_for and redirect in flask.

nginx.conf entry:

location /flaskapp {
  proxy_pass http://myapp:8080/;
  proxy_set_header Host $http_host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

The idea is that a user navigates to

https://localhost:port/flaskapp/some/location/here

and that should be passed to flask as

http://localhost:8080/some/location/here

This works reasonably well when navigating to a defined route, however if the route has redirect(url_for('another_page')), the browser is directed to

http://localhost:8080/another_page

And fails, when the URL I actually want to go to is:

https://localhost:port/flaskapp/another_page

I have tried several other answers for similar situations, but none have seemed to be doing exactly what I am doing here. I have tried using _external=True, setting app.config['APPLICATION_ROOT'] = '/flaskapp' and many iterations of different proxy_set_header commands in nginx.conf with no luck.

As an added complication, my flask application is using flask-login and CSRF cookies. When I tried setting APPLICATION_ROOT the application stopped considering the CSRF cookie set by flask-login valid, which I assume has something to do with origins.

So my question is, how do I make it so that when flask is returning a redirect() to the client, nginx understands that the URL it is given needs flaskapp written into it?

mstorkson
  • 1,130
  • 1
  • 10
  • 26
  • Have you tried setting `SCRIPT_NAME`? See [here](https://gist.github.com/Larivact/1ee3bad0e53b2e2c4e40) for more info. – noslenkwah Oct 30 '19 at 20:39
  • @noslenkwah doesn't do anything. I'm unclear on if I can set that variable with `app.config['SCRIPT_NAME'] = '/myapp' or if I have to use some kind of middleware transformation. – mstorkson Oct 30 '19 at 20:57
  • That's not how you set it. See the link in the previous comment. – noslenkwah Oct 30 '19 at 21:49

2 Answers2

6

I managed to fix it with some changes.

Change 1. Adding /flaskapp to the routes in my flask application. This eliminated the need for URL-rewriting and simplified things greatly.

Change 2. nginx.conf changes. I added logc in the location block to redirect http requests as https, new conf:

location /flaskapp {
  proxy_pass http://myapp:8080/;
  proxy_set_header Host $http_host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  # New configs below
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-Proto $scheme;
  # Makes flask redirects use https, not http.
  proxy_redirect http://$http_host/ https://$http_host/;
}

While I didn't "solve" the issue of introducing conditional rewrites based on a known prefix, since I only need one prefix for this app it is an acceptable solution to bake it into the routes.

Basil Musa
  • 8,198
  • 6
  • 64
  • 63
mstorkson
  • 1,130
  • 1
  • 10
  • 26
1

In your situation I think the correct thing would be to use werkzeug's ProxyFix middleware, and have your nginx proxy set the appropriate required headers (specifically X-Forwarded-Prefix).

https://werkzeug.palletsprojects.com/en/0.15.x/middleware/proxy_fix/#module-werkzeug.middleware.proxy_fix

This should make url_for work as you would expect.

Edit: Snippet from @Michael P's answer

from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)
Tyrannas
  • 4,303
  • 1
  • 11
  • 16
niltz
  • 1,014
  • 11
  • 28
  • Thanks @niltz for saving my day. Nevertheless a code snippet would be nice. Here it is: `from werkzeug.middleware.proxy_fix import ProxyFix ; app = Flask(__name__) ; app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)` – Michael P Jul 08 '21 at 14:30