403 : Forbidden XSRF cookie does not match POST argument

Hi, I wanted to allocate a non-interfering jupyterlab environment for multiple users, so I tried jupyterhub + dockerspawner. Currently, I can allocate available jupyterlab environments for different users, each of which depends on its own docker container, which meets my needs.

However, I had XSRF issues when I wanted to embed a user’s jupyterlab page into another web page. My related config and logs are as follows.

Related package versions:

  • jupyterhub 4.1.6
  • jupyterlab 4.3.6
  • dockerspawner 13.0.0

The configuration file is as follows:

import os
from dockerspawner import DockerSpawner

c = get_config() 

c.JupyterHub.hub_ip = '0.0.0.0'
c.JupyterHub.hub_port = 8005

c.PAMAuthenticator.encoding = 'utf8'
c.Authenticator.whitelist = {'root','admin', 'jupyter', 'user4'}

c.LocalAuthenticator.create_system_users = True
c.Authenticator.admin_users = {'root', 'admin', 'coderz'}

c.Spawner.cmd = ['jupyter', 'labhub', '--allow-root']

c.DockerSpawner.start_timeout = 120
c.DockerSpawner.http_timeout = 120

c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'

notebook_dir = '/home/jovyan/work'
c.DockerSpawner.notebook_dir = notebook_dir

c.DockerSpawner.environment = {
    "CHOWN_HOME": "yes",
    "CHOWN_EXTRA": "/home/jovyan",
    "CHOWN_HOME_OPTS": "-R",
    "NB_UID": 1000,
    "NB_GID": 1000,
}

c.DockerSpawner.volumes = { 'jupyterhub-user-{username}': notebook_dir }

c.JupyterHub.tornado_settings = {'headers': {'Content-Security-Policy': "frame-ancestors *;"}}

c.Spawner.args = [
    '--VoilaConfiguration.enable_nbextensions=True',
    "--VoilaConfiguration.file_whitelist=['.*']",
    '--NotebookApp.tornado_settings={"headers":{"Content-Security-Policy": "frame-ancestors *;"}}']

import logging
c.JupyterHub.log_level = logging.DEBUG

Example embedding page:

<!DOCTYPE html>
<html>
<body>
  <iframe src="http://127.0.0.1:8000/user/coderz/lab" width="100%" height="600"></iframe>
</body>
</html>

The terminal log looks like this:

[I 2025-04-15 10:59:10.711 JupyterHub log:192] 302 GET /hub/api/oauth2/authorize?client_id=jupyterhub-user-coderz&redirect_uri=%2Fuser%2Fcoderz%2Foauth_callback&response_type=code&state=[secret] -> /hub/login?next=%2Fhub%2Fapi%2Foauth2%2Fauthorize%3Fclient_id%3Djupyterhub-user-coderz%26redirect_uri%3D%252Fuser%252Fcoderz%252Foauth_callback%26response_type%3Dcode%26state%3DeyJ1dWlkIjogIjYwZGZhMTRiNzRkYzQ4MDdhY2Q2ZGE0Njc1Mzk4YWNmIiwgIm5leHRfdXJsIjogIi91c2VyL2NvZGVyei9sYWIifQ (@::ffff:127.0.0.1) 0.51ms
[I 2025-04-15 10:59:10.714 JupyterHub _xsrf_utils:125] Setting new xsrf cookie for b'None:oBVBPOr473tun1Cs7gtN3o2i4XhsPOtFlt258C__ZBk=' {'path': '/hub/', 'max_age': 3600}
[I 2025-04-15 10:59:10.715 JupyterHub log:192] 200 GET /hub/login?next=%2Fhub%2Fapi%2Foauth2%2Fauthorize%3Fclient_id%3Djupyterhub-user-coderz%26redirect_uri%3D%252Fuser%252Fcoderz%252Foauth_callback%26response_type%3Dcode%26state%3DeyJ1dWlkIjogIjYwZGZhMTRiNzRkYzQ4MDdhY2Q2ZGE0Njc1Mzk4YWNmIiwgIm5leHRfdXJsIjogIi91c2VyL2NvZGVyei9sYWIifQ (@::ffff:127.0.0.1) 1.39ms
[D 2025-04-15 10:59:10.766 JupyterHub log:192] 304 GET /hub/static/css/style.min.css.map (@::ffff:127.0.0.1) 0.73ms
[D 2025-04-15 10:59:14.295 JupyterHub proxy:924] Proxy: Fetching GET http://127.0.0.1:8001/api/routes
10:59:14.298 [ConfigProxy] info: 200 GET /api/routes 
[D 2025-04-15 10:59:14.307 JupyterHub proxy:393] Checking routes
[I 2025-04-15 10:59:15.889 JupyterHub _xsrf_utils:125] Setting new xsrf cookie for b'None:oBVBPOr473tun1Cs7gtN3o2i4XhsPOtFlt258C__ZBk=' {'path': '/hub/', 'max_age': 3600}
[W 2025-04-15 10:59:15.890 JupyterHub web:1873] 403 POST /hub/login?next=%2Fhub%2Fapi%2Foauth2%2Fauthorize%3Fclient_id%3Djupyterhub-user-coderz%26redirect_uri%3D%252Fuser%252Fcoderz%252Foauth_callback%26response_type%3Dcode%26state%3DeyJ1dWlkIjogIjYwZGZhMTRiNzRkYzQ4MDdhY2Q2ZGE0Njc1Mzk4YWNmIiwgIm5leHRfdXJsIjogIi91c2VyL2NvZGVyei9sYWIifQ (::ffff:127.0.0.1): XSRF cookie does not match POST argument
[D 2025-04-15 10:59:15.890 JupyterHub base:1471] No template for 403
[W 2025-04-15 10:59:15.890 JupyterHub log:192] 403 POST /hub/login?next=%2Fhub%2Fapi%2Foauth2%2Fauthorize%3Fclient_id%3Djupyterhub-user-coderz%26redirect_uri%3D%252Fuser%252Fcoderz%252Foauth_callback%26response_type%3Dcode%26state%3DeyJ1dWlkIjogIjYwZGZhMTRiNzRkYzQ4MDdhY2Q2ZGE0Njc1Mzk4YWNmIiwgIm5leHRfdXJsIjogIi91c2VyL2NvZGVyei9sYWIifQ (@::ffff:127.0.0.1) 1.09ms
[D 2025-04-15 10:59:15.926 JupyterHub log:192] 304 GET /hub/static/css/style.min.css.map (@::ffff:127.0.0.1) 0.36ms
[D 2025-04-15 10:59:21.101 JupyterHub dockerspawner:1027] Getting container 'jupyter-coderz'
[D 2025-04-15 10:59:21.103 JupyterHub dockerspawner:1012] Container af6c0ac status: {'Dead': False,
     'Error': '',
     'ExitCode': 0,
     'FinishedAt': '2025-04-15T02:29:13.132442954Z',
     'Health': {'FailingStreak': 0,
                'Log': [{'End': '2025-04-15T10:59:07.717961374+08:00',
                         'ExitCode': 0,
                         'Output': 'b\'{"version": "2.14.2"}\'\n',
                         'Start': '2025-04-15T10:59:07.575552865+08:00'},
                        {'End': '2025-04-15T10:59:10.880294612+08:00',
                         'ExitCode': 0,
                         'Output': 'b\'{"version": "2.14.2"}\'\n',
                         'Start': '2025-04-15T10:59:10.718792457+08:00'},
                        {'End': '2025-04-15T10:59:14.02023307+08:00',
                         'ExitCode': 0,
                         'Output': 'b\'{"version": "2.14.2"}\'\n',
                         'Start': '2025-04-15T10:59:13.881877362+08:00'},
                        {'End': '2025-04-15T10:59:17.167927882+08:00',
                         'ExitCode': 0,
                         'Output': 'b\'{"version": "2.14.2"}\'\n',
                         'Start': '2025-04-15T10:59:17.020703485+08:00'},
                        {'End': '2025-04-15T10:59:20.18264585+08:00',
                         'ExitCode': 0,
                         'Output': 'b\'{"version": "2.14.2"}\'\n',
                         'Start': '2025-04-15T10:59:20.036248153+08:00'}],
                'Status': 'healthy'},
     'OOMKilled': False,
     'Paused': False,
     'Pid': 20289,
     'Restarting': False,
     'Running': True,
     'StartedAt': '2025-04-15T02:29:20.668404345Z',
     'Status': 'running'}

I have tried to clear the browser cookies, restart jupyterhub service, and also tried to modify jupyterhub_config.py, but this issue has not been resolved yet.

How can I fix this problem? Or are there other relevant solutions to address the above requirements?