encode/starlette-example

`url_for` returns http instead of https

naokipeter opened this issue · 7 comments

When I deploy the app on Heroku [1], all the url_fors in base.html give me URLs with http instead of https, which triggers a Mixed Content error. When I run uvicorn locally using ssl_keyfile and ssl_certfile, I don't get this problem.

Links:
[1] https://aqueous-waters-64836.herokuapp.com/

You may need uvicorn's --proxy-headers setting.

Have a dig into what headers Heroku sets to inform the application about what scheme was used to make the connection.

I used the options for Uvicorn (--proxy-headers) and Gunicorn (--forwarded-allow-ips="*") to pass on X-Forwarded-Proto in environments with a trusted proxy (e.g. on Heroku), but, as far as I can tell from looking at templating:url_for, requests:url_for and routing:url_path_for , the only thing Starlette looks at when determining the protocol is the base URL.

Right, and the --proxy-headers effect is to set the scheme correctly https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py#L26 so that the application level re- constructs the incoming URL with the HTTP scheme as seen by the proxy, rather than the HTTP scheme on which the proxy connected to the application.

You'll want to have a dig into whatever custom headers your deployment environment is setting, in order to inform the application about the actual scheme of the originating request, and also take a look at the ASGI messages that are being passed to the application.

The request headers contain 'x-forwarded-proto':'https', but the scope has 'scheme': 'http'. Could it be that sslcontext isn't recognized properly by Uvicorn?

request.headers:

{  
   'host':'aqueous-waters-64836.herokuapp.com',
   'connection':'close',
   'cache-control':'max-age=0',
   'upgrade-insecure-requests':'1',
   'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
   'accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
   'referer':'https://dashboard.heroku.com/',
   'accept-encoding':'gzip, deflate, br',
   'accept-language':'en-US,en;q=0.9,de-CH;q=0.8,de;q=0.7,ja;q=0.6,fr;q=0.5',
   'x-request-id':'2be84343-c7c8-48dd-9762-0f124c7c8dd0',
   'x-forwarded-for':'51.154.88.136',
   'x-forwarded-proto':'https',
   'x-forwarded-port':'443',
   'via':'1.1 vegur',
   'connect-time':'0',
   'x-request-start':'1559493926662',
   'total-route-time':'0'
}

request._scope:

{
  'type': 'http',
  'http_version': '1.1',
  'server': ('172.19.29.62',
  20081),
  'client': ('10.63.95.16',
  33910),
  'scheme': 'http',
  'method': 'GET',
  'root_path': '',
  'path': '/',
  'query_string': b'',
  'headers': [
    (b'host',
    b'aqueous-waters-64836.herokuapp.com'),
    (b'connection',
    b'close'),
    (b'upgrade-insecure-requests',
    b'1'),
    (b'user-agent',
    b'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'),
    (b'accept',
    b'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3'),
    (b'accept-encoding',
    b'gzip, deflate, br'),
    (b'accept-language',
    b'en-US,en;q=0.9,de-CH;q=0.8,de;q=0.7,ja;q=0.6,fr;q=0.5'),
    (b'x-request-id',
    b'c3418f86-aece-4c5a-a3dd-8c70cdd095bc'),
    (b'x-forwarded-for',
    b'217.192.56.102'),
    (b'x-forwarded-proto',
    b'https'),
    (b'x-forwarded-port',
    b'443'),
    (b'via',
    b'1.1 vegur'),
    (b'connect-time',
    b'1'),
    (b'x-request-start',
    b'1559656483407'),
    (b'total-route-time',
    b'0')
  ],
  'app': <starlette.applications.Starletteobjectat0x7efda97c5278>,
  'router': <starlette.routing.Routerobjectat0x7efda97c52b0>,
  'endpoint': <functionhomepageat0x7efda97c0a60>,
  'path_params': {
    
  }
}

await request.receive():

{'type': 'http.request', 'body': b'', 'more_body': False}

What does your Procfile look like?

web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker --forwarded-allow-ips="*" app:app

Okay thanks! That helps tease out the root issue from this - encode/uvicorn#369 - will continue any conversation on that one instead.

Short: we don't have any equivalent for the --forwarded-allow-ips="*" gunicorn setting in uvicorn. A quick fix might be to special case * into proxy_headers=True. Tho really we ought to match gunicorn's settings more closely here.