Send POST request to `/hub/spawn/` from a service does not work in 4.1.2
trungleduc opened this issue · comments
Bug description
In our tljh-repo2docker
service, we have a standalone UI to spawn the server by sending a POST request to the /hub/spawn/
endpoint. It works as expected up to 4.0.x
.
From 4.1.2, it does not work anymore with the following error message:
[WebServer] 403 POST /hub/spawn/alice/test-server?_xsrf=MnwxOjB8...TEwODY1MmEwMmMzYQ (127.0.0.1): XSRF cookie does not match POST argument
I think it's related to #4750, since we are sending the _xsrf
cookie at path /services/tljh_repo2docker/
but jupyterhub
is picking the cookie at /hub/
to do the comparison.
Is there anything we can do from our side to fix this issue?
Thanks!
How to reproduce
- Install a dev version of
tljh-repo2docker
- Go to
http://127.0.0.1:8000/services/tljh_repo2docker/servers
then start a new server
Expected behaviour
The POST request finishes without error
Actual behaviour
Your personal set up
- OS:
- Version(s):
jupyterhub 4.1.2
Full environment
# paste output of `pip freeze` or `conda list` here
Configuration
# jupyterhub_config.py
Logs
cc @jtpio
We inject the xsrf_token
to the page via the jinja2
template
class BaseHandler(HubOAuthenticated, web.RequestHandler):
...
async def render_template(self, name: str, **kwargs) -> str:
template_ns = dict(
...
xsrf_token=self.xsrf_token.decode("ascii"),
)
template_ns.update(kwargs)
template = self.get_template(name)
return template.render(**template_ns)
...
https://github.com/plasmabio/tljh-repo2docker/blob/master/tljh_repo2docker/base.py#L92
Yes, this request shouldn't work, which is fixed in 4.1. Instead, the service should request permission to spawn via OAuth scopes (service.oauth_client_allowed_scopes in config, or be given permission to spawn on behalf of users without any user intervention via role assignment to the service itself) and use the resulting token to make the launch request via the API. Using tokens to authenticate requests eliminates all xsrf issues. The request can be the same originating from the browser, but put the oauth token in the Authorization header instead of the xsrf token in the URL.
c.JupyterHub.services = [
{
"name": "myservice",
"oauth_client_allowed_scopes": ["servers!user"],
...
}
]
Thank @minrk for the pointer! We adopted the new approach and it works nicely.