async def test_singleuser_xsrf(
    app,
    browser,
    user,
    create_user_with_scopes,
    full_spawn,
    named_servers,  # noqa: F811
):
    # full login process, checking XSRF handling
    # start two servers
    target_user = user
    target_start = asyncio.ensure_future(target_user.spawn())

    browser_user = create_user_with_scopes("self", "access:servers")
    # login browser_user
    login_url = url_path_join(public_host(app), app.hub.base_url, "login")
    await browser.goto(login_url)
    await login(browser, browser_user.name, browser_user.name)
    # end up at single-user
    await expect(browser).to_have_url(re.compile(rf".*/user/{browser_user.name}/.*"))
    # wait for target user to start, too
    await target_start
    await app.proxy.add_user(target_user)

    # visit target user, sets credentials for second server
    await browser.goto(public_url(app, target_user))
    await expect(browser).to_have_url(re.compile(r".*/oauth2/authorize"))
    auth_button = browser.locator('//button[@type="submit"]')
    await expect(auth_button).to_be_enabled()
    await auth_button.click()
    await expect(browser).to_have_url(re.compile(rf".*/user/{target_user.name}/.*"))

    # at this point, we are on a page served by target_user,
    # logged in as browser_user
    # basic check that xsrf isolation works
    cookies = await browser.context.cookies()
    cookie_dict = _cookie_dict(cookies)
    pprint.pprint(cookie_dict)

    # we should have xsrf tokens for both singleuser servers and the hub
    target_prefix = target_user.prefix
    user_prefix = browser_user.prefix
    hub_prefix = app.hub.base_url
    assert target_prefix in cookie_dict
    assert user_prefix in cookie_dict
    assert hub_prefix in cookie_dict
    target_xsrf = cookie_dict[target_prefix].get("_xsrf", {}).get("value")
    assert target_xsrf
    user_xsrf = cookie_dict[user_prefix].get("_xsrf", {}).get("value")
    assert user_xsrf
    hub_xsrf = cookie_dict[hub_prefix].get("_xsrf", {}).get("value")
    assert hub_xsrf
    assert hub_xsrf != target_xsrf
    assert hub_xsrf != user_xsrf
    assert target_xsrf != user_xsrf

    # we are on a page served by target_user
    # check that we can't access

    async def fetch_user_page(path, params=None):
        url = url_path_join(public_url(app, browser_user), path)
        if params:
            url = url_concat(url, params)
        status = await browser.evaluate(
            """
            async (user_url) => {
              try {
                response = await fetch(user_url);
              } catch (e) {
                return 'error';
              }
              return response.status;
            }
            """,
            url,
        )
        return status

    if app.subdomain_host:
        expected_status = 'error'
    else:
        expected_status = 403
    status = await fetch_user_page("/api/contents")
    assert status == expected_status
    status = await fetch_user_page("/api/contents", params={"_xsrf": target_xsrf})
    assert status == expected_status

    if not app.subdomain_host:
        expected_status = 200
    status = await fetch_user_page("/api/contents", params={"_xsrf": user_xsrf})
    assert status == expected_status

    # check that we can't iframe the other user's page
    async def iframe(src):
        return await browser.evaluate(
            """
            async (src) => {
                const frame = document.createElement("iframe");
                frame.src = src;
                return new Promise((resolve, reject) => {
                    frame.addEventListener("load", (event) => {
                        if (frame.contentDocument) {
                            resolve("got document!");
                        } else {
                            resolve("blocked")
                        }
                    });
                    setTimeout(() => {
                        // some browsers (firefox) never fire load event
                        // despite spec appasrently stating it must always do so,
                        // even for rejected frames
                        resolve("timeout")
                    }, 3000)

                    document.body.appendChild(frame);
                });
            }
            """,
            src,
        )

    hub_iframe = await iframe(url_path_join(public_url(app), "hub/admin"))
    assert hub_iframe in {"timeout", "blocked"}
    user_iframe = await iframe(public_url(app, browser_user))
    assert user_iframe in {"timeout", "blocked"}

    # check that server page can still connect to its own kernels
    token = target_user.new_api_token(scopes=["access:servers!user"])

    async def test_kernel(kernels_url):
        headers = {"Authorization": f"Bearer {token}"}
        r = await async_requests.post(kernels_url, headers=headers)
        r.raise_for_status()
        kernel = r.json()
        kernel_id = kernel["id"]
        kernel_url = url_path_join(kernels_url, kernel_id)
        kernel_ws_url = "ws" + url_path_join(kernel_url, "channels")[4:]
        try:
            result = await browser.evaluate(
                """
                async (ws_url) => {
                    ws = new WebSocket(ws_url);
                    finished = await new Promise((resolve, reject) => {
                        ws.onerror = (err) => {
                            reject(err);
                        };
                        ws.onopen = () => {
                            resolve("ok");
                        };
                    });
                    return finished;
                }
                """,
                kernel_ws_url,
            )
        finally:
            r = await async_requests.delete(kernel_url, headers=headers)
            r.raise_for_status()
        assert result == "ok"

    kernels_url = url_path_join(public_url(app, target_user), "/api/kernels")
    await test_kernel(kernels_url)

    # final check: make sure named servers work.
    # first, visit spawn page to launch server,
    # will issue cookies, etc.
    server_name = "named"
    url = url_path_join(
        public_host(app),
        url_path_join(app.base_url, f"hub/spawn/{browser_user.name}/{server_name}"),
    )
    await browser.goto(url)
    await expect(browser).to_have_url(
        re.compile(rf".*/user/{browser_user.name}/{server_name}/.*")
    )
    # from named server URL, make sure we can talk to a kernel
    token = browser_user.new_api_token(scopes=["access:servers!user"])
    # named-server URL
    kernels_url = url_path_join(
        public_url(app, browser_user), server_name, "api/kernels"
    )
    await test_kernel(kernels_url)
    # go back to user's own page, test again
    # make sure we didn't break anything
    await browser.goto(public_url(app, browser_user))
    await test_kernel(url_path_join(public_url(app, browser_user), "api/kernels"))
async (user_url) => {
try {
response = await fetch(user_url);
} catch (e) {
return 'error';
}
return response.status;
}
""",
url,
)
return status

if app.subdomain_host:
expected_status = 'error'
else:
expected_status = 403
status = await fetch_user_page("/api/contents")
assert status == expected_status
status = await fetch_user_page("/api/contents", params={"_xsrf": target_xsrf})
assert status == expected_status

if not app.subdomain_host:
expected_status = 200
status = await fetch_user_page("/api/contents", params={"_xsrf": user_xsrf})
assert status == expected_status

# check that we can't iframe the other user's page
async def iframe(src):
return await browser.evaluate(
jupyterhub/tests/browser/test_browser.py