diff --git a/jupyterhub/apihandlers/base.py b/jupyterhub/apihandlers/base.py index 8994b659..73b129d8 100644 --- a/jupyterhub/apihandlers/base.py +++ b/jupyterhub/apihandlers/base.py @@ -58,7 +58,7 @@ class APIHandler(BaseHandler): - allow unspecified host/referer (e.g. scripts) """ - host = self.request.headers.get("Host") + host = self.request.headers.get(self.app.forwarded_host_header or "Host") referer = self.request.headers.get("Referer") # If no header is provided, assume it comes from a script/curl. diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 19160189..753cb245 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -791,6 +791,16 @@ class JupyterHub(Application): self.proxy_api_ip or '127.0.0.1', self.proxy_api_port or self.port + 1 ) + forwarded_host_header = Unicode( + '', + help="""Alternate header to use as the Host (e.g., X-Forwarded-Host) + when determining whether a request is cross-origin + + This may be useful when JupyterHub is running behind a proxy that rewrites + the Host header. + """, + ).tag(config=True) + hub_port = Integer( 8081, help="""The internal port for the Hub process. diff --git a/jupyterhub/tests/test_api.py b/jupyterhub/tests/test_api.py index d546c410..961be214 100644 --- a/jupyterhub/tests/test_api.py +++ b/jupyterhub/tests/test_api.py @@ -136,6 +136,32 @@ async def test_cors_checks(app): ) assert r.status_code == 400 # accepted, but invalid + app.forwarded_host_header = 'X-Forwarded-Host' + r = await api_request( + app, + 'users', + headers={ + 'Authorization': '', + 'Referer': url, + 'Host': host, + 'X-Forwarded-Host': 'example.com', + }, + cookies=cookies, + ) + assert r.status_code == 403 + + r = await api_request( + app, + 'users', + headers={ + 'Authorization': '', + 'Referer': url, + 'Host': host, + 'X-Forwarded-Host': host, + }, + cookies=cookies, + ) + assert r.status_code == 200 # -------------- # User API tests