Issue with chrome 80 and SameSite cookie changes

Hi there

I run my own notebook hosting platform

a few weeks ago some users have complained about getting a 403 Forbidden when loading their notebook; I have started to document my findings on this matter

it appears the issue has to see with a recent change in chrome, being currently rolled out, and outlined in this post

I was wondering if anybody had been exposed to something similar recently

I ran into this issue while Chrome initially rolled out:

However, this is still currently easily reproducible with Chrome Canary (or by simply opting in to the SameSite changes.)

Jupyter sends Set-Cookie for _xsrf with no SameSite property, so Chrome rejects it. Then a Jupyter action like creating a new text file will fail.

As a solution, I made my middleman proxy read these Set-Cookie headers and write them back with SameSite=None and Secure. The _xsrf cookie goes through now. But who knows what else could be wrong? Embedding the notebook is officially documented and seemingly not working.

I’m stuck with the same issue right now. How did you create this middleman proxy? Did you use nginx or is there a way to use the configurable-http-proxy that jupyterhub uses. Would you be able to share your thoughts?

Can you share details of your setup? I’m not aware of how jupyterhub works. The TL;DR here is that you need the Set-Cookie header for _xsrf to be modified to include SameSite=None and Secure, or follow to disable the need for the _xsrf cookie completely.

Right now I have these configurations in

     c.JupyterHub.tornado_settings = {
        'headers': {
          'Content-Security-Policy': "frame-ancestors 'self' http://localhost:5001 <url> <url> ",
        'cookie_options': {'SameSite': 'None', 'Secure': True}
      c.Spawner.args = ["--NotebookApp.tornado_settings={\"headers\":{\"Content-Security-Policy\": \"frame-ancestors 'self' http://localhost:5001 <url> <url> \"}}"]
      c.NotebookApp.open_browser = False

They work perfectly with Firefox, but it doesnt set the cookie attributes on Chrome. It’s like it doesn’t recognize the cookie_options

anyone know if there’s a way around this that doesn’t require a proxy?

We resolved this by upgrading the Notebook and JupyterHub images by adding additional upstream Tornado-based configs and upgrading to Python 3.8:

Jupyter Notebook(s)

  • If using repo2docker set python to 3.8 with either the environment.yml or runtime.txt
  • If using jupyter/docker-stacks -based image, build the base-image by setting the PYTHON_VERSION argument.
  • Update with c.NotebookApp.tornado_settings = {"cookie_options": {"SameSite": "None", "Secure": True}}


  • If using the standard jupyterhub/jupyterhub image then either rebuild the image by setting the version to 3.8 or override the current python version (which is 3.6.9 last time we checked).
  • The above also applies when using the JupyterHub Kubernetes-based image, jupyerhub/k8s-hub.
  • Update the with c.JupyterHub.tornado_settings = {"cookie_options": {"SameSite": "None", "Secure": True}}

Hope this helps!


@jgwerner ohhhh I see. By any chance, do you have your JupyterHub image with python 3.8 on a public registry? Looks like we did use the kubernetes-based image which had python version 3.6.9 set

it worked once we rebuilt the kubernetes-based image to have Python version 3.8 (our notebook image already was at python version 3.8 and we already had both tornado_settings configs)! Thank you so much!

Thank you, @jgwerner! This fixed the issue

@maluhoss Yes we rebuilt the standard jupyterhub/jupyterhub image by using python:3.8-buster as the base image. Then rebuilt the k8s image by inheriting from the rebuilt base image and adding the layers needed to work with kubernetes (NB_UID, NB_USER, packages, etc).

If you are using the oauthenticator, you may need to override the set_state_cookie method from the OAuthLoginHandler class:

    def set_state_cookie(self, state):
        Add 'samesite' and 'secure' parameters. Requires Python 3.8.* to work.
        self.set_secure_cookie(STATE_COOKIE_NAME, state, expires_days=1, httponly=True, samesite=None, secure=True)

This was a fix that was implemented by @netoisc - I’m sure he could offer more details!

1 Like