Set_login_cookie dont change the cookie in the current request

Hi team, I’m working on a custom jupyterhub that works only in API mode and has my custom kubespawner and authenticator. I’m facing a cookie-related issue when a user authenticates in the system using my authenticator. I set the login cookie using self.set_login_cookie(user) in the log I get [D 2024-03-18 15:37:36.366 JupyterHub base:587] Setting cookie jupyterhub-hub-login: {‘httponly’: True, ‘secure’: True, ‘SameSite’: ‘None’, ‘Secure’: True, ‘path’: ‘/hub/’} but then I try to log the cookie using self.log.info(f’Request cookie: {self.get_cookie(self.hub.cookie_name)}') and get Request cookie: None, also try self.get_current_user_cookie(), same results. The following request that I send has the cookie correctly set Request cookie: 2|1:0|10:1710775831|20:jupyterhub-hub-login|44:Mzc1ZjFhYTFlMGVjNDQwNmE4ODE0ZTJlMjUzNDUzMjk=|410a05772cc71b1db67d01989d8dc8e0ae64e710fdde08fd87ffd391756ed11b

The problem I got is that the first request always fails because after the 302 redirects, it calls my login logic again, but the token that arrived the first time in the headers doesn’t exist anymore and doesn’t have a valid cookie either.

Any clues in what could happen here?

Can I ask why you are trying to take over setting login cookies? Normally, Authenticators only implement the authenticate() method, which returns the user info and JupyterHub itself is responsible for setting the cookie.

Can you share more about the sequence of requests you expect and what you see instead? Logs and jupyterhub configuration will be helpful, too. And a link to your custom Authenticator, if you can.

Hi Minrk, in my system, we use Jupyter Notebook 7 embedded in an iframe. I have a requirement to avoid the login screen from Jupyter, so the authentication came from our system; this authentication could be from a token in the header, a token in the URL, or a cookie. In my Authenticator, I define a custom login handler to deal with this:

def get_handlers(self, app: Any) -> List[Tuple[str, Type[RequestHandler]]]:
       return [
           (r"/login", JSONWebTokenLoginHandler),
       ]

I don’t override the authenticate because when I try that, I get the Jupyter login screen,

In that Login handler I got:

# Extract the JWT token from the request (header, URL argument, or cookie)
jwt_token = self._extract_jwt_token(handler)
# Validate the token
if self._validate_jwt_token(jwt_token):
    claims = None
    if secret:
        claims = verify_jwt_using_secret(token, secret, audience)

    if claims is None:
        self.log.error("Invalid claims.")
         raise web.HTTPError(401)

    username = retrieve_username(claims, username_claim_field)
    user = self.user_from_username(username)
    self.log.info(f"Username from claims: {username}. User for set cookie: {user}")
    
    # Clean cookie if the user is not correct
    jupyterhub_cookie = self.get_current_user_cookie()
      if jupyterhub_cookie:
          cookie_user_name = jupyterhub_cookie.name
          if cookie_user_name != username:
              self.clear_cookie(self.hub.cookie_name, path=self.hub.base_url)
                self.log.info(f'User cookie cleared: {cookie_user_name}')

        self.set_login_cookie(user)
      
        self.log.info(f'Request cookies: {self.request.cookies}')

        _url = self.get_redirect_url(user, token)

        self.log.info(f"Redirect to URL={_url}")
        self.redirect(_url, permanent=False)   

The iframe could be embedded in different web apps, and for each app, I got a different user that I used to create the server. The problem I got is that in the same web browser, a user can open a notebook from two different apps; this ends with the first notebook loading ok but the second one failing the auth because it is trying to open the server with the wrong cookie. To avoid this, I add this code:

 jupyterhub_cookie = self.get_current_user_cookie()
 if jupyterhub_cookie:
      cookie_user_name = jupyterhub_cookie.name
       if cookie_user_name != username:
            self.clear_cookie(self.hub.cookie_name, path=self.hub.base_url)
            self.log.info(f'User cookie cleared: {cookie_user_name}')

This ends with the Jupyterhub cookie removed but the authentication don’t pass because the redirect doesn’t have any valid cookie even when I add the self.set_login_cookie(user).

These are examples of the log when the auth is ok:

The server for user 82c20951-9587-4691-812d-7cd49b1a42f5 has been created.
    [I 2024-03-20 09:51:24.994 JupyterHub authenticator:137] Header name: Authorization 
    QueryParam name: token 
    Header is auth: True 
    Header content:  
    Cookie content:  
    Hub cookie content:  
    Secret: secret-secret-secret 
    Username claim field: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier 
    Audience:  
    Token param: **VALID JWT TOKEN**
[I 2024-03-20 09:51:24.996 JupyterHub authenticator:88] Username from claims: 82c20951-9587-4691-812d-7cd49b1a42f5. User for set cookie: <User(82c20951-9587-4691-812d-7cd49b1a42f5 1/2 running)>
[D 2024-03-20 09:51:24.996 JupyterHub base:591] Setting cookie for 82c20951-9587-4691-812d-7cd49b1a42f5: jupyterhub-hub-login
[D 2024-03-20 09:51:24.996 JupyterHub base:587] Setting cookie jupyterhub-hub-login: {'httponly': True, 'secure': True, 'SameSite': 'None', 'Secure': True, 'path': '/hub/'}
[I 2024-03-20 09:51:24.997 JupyterHub authenticator:99] Request cookies: Set-Cookie: .AspNetCore.Cookies=SOME VALUE

    Set-Cookie: IsLoggedOrionUser=True

    Set-Cookie: _xsrf=2|0ad9a02b|bdea3d7f6f852b65541a3a9e995d9025|1710501277

    Set-Cookie: jupyterhub-session-id=b5b4aba54a7e4156b76cd69e07958fed
[W 2024-03-20 09:51:24.997 JupyterHub base:701] Redirecting /hub/login?token=VALID TOKEN&next=%2Fuser%2F82c20951-9587-4691-812d-7cd49b1a42f5%2Forionhubnsuser.0.25%2Fnotebooks%2F10%2Fdocument%2F5495%2FNew+Document+8862.ipynb&theme=theme-light to /hub/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New Document 8862.ipynb. For sharing public links, use /user-redirect/
[I 2024-03-20 09:51:24.997 JupyterHub authenticator:103] Redirect authentication to URL=/hub/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New Document 8862.ipynb?theme=theme-light?token=VALID TOKEN
[I 2024-03-20 09:51:24.998 JupyterHub log:191] 302 GET /hub/login?token=[secret]&next=%2Fuser%2F82c20951-9587-4691-812d-7cd49b1a42f5%2Forionhubnsuser.0.25%2Fnotebooks%2F10%2Fdocument%2F5495%2FNew+Document+8862.ipynb&theme=theme-light -> /hub/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New Document 8862.ipynb?theme=theme-light?token=VALID TOKEN (@192.168.65.4) 4.29ms
[D 2024-03-20 09:51:25.014 JupyterHub scopes:877] Checking access to /hub/api/users/82c20951-9587-4691-812d-7cd49b1a42f5/servers/orionhubnsuser.0.25/progress via scope read:servers
[D 2024-03-20 09:51:25.014 JupyterHub scopes:677] Unrestricted access to /hub/api/users/82c20951-9587-4691-812d-7cd49b1a42f5/servers/orionhubnsuser.0.25/progress via read:servers
[I 2024-03-20 09:51:25.016 JupyterHub users:725] Server 82c20951-9587-4691-812d-7cd49b1a42f5:orionhubnsuser.0.25 is already started
[I 2024-03-20 09:51:25.016 JupyterHub log:191] 200 GET /hub/api/users/82c20951-9587-4691-812d-7cd49b1a42f5/servers/orionhubnsuser.0.25/progress (orionendpoints@127.0.0.1) 17.69ms
[D 2024-03-20 09:51:25.063 JupyterHub scopes:877] Checking access to /hub/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New%20Document%208862.ipynb via scope access:servers
[D 2024-03-20 09:51:25.063 JupyterHub scopes:690] Argument-based access to /hub/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New%20Document%208862.ipynb via access:servers
[I 2024-03-20 09:51:25.064 JupyterHub log:191] 302 GET /hub/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New%20Document%208862.ipynb?theme=theme-light?token=VALID TOKEN -> /user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/notebooks/10/document/5495/New%20Document%208862.ipynb?theme=theme-light%3Ftoken%3DVALID TOKEN&redirects=1 (82c20951-9587-4691-812d-7cd49b1a42f5@192.168.65.4) 4.52ms
[D 2024-03-20 09:51:25.113 JupyterHub provider:420] Validating client id jupyterhub-user-82c20951-9587-4691-812d-7cd49b1a42f5-orionhubnsuser.0.25
[D 2024-03-20 09:51:25.116 oauthlib.oauth2.rfc6749.grant_types.authorization_code authorization_code:362] Validating redirection uri /user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/oauth_callback for client jupyterhub-user-82c20951-9587-4691-812d-7cd49b1a42f5-orionhubnsuser.0.25.
[D 2024-03-20 09:51:25.116 oauthlib.oauth2.rfc6749.grant_types.base base:231] Using provided redirect_uri /user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/oauth_callback
[D 2024-03-20 09:51:25.116 JupyterHub provider:495] validate_redirect_uri: client_id=jupyterhub-user-82c20951-9587-4691-812d-7cd49b1a42f5-orionhubnsuser.0.25, redirect_uri=/user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/oauth_callback
[D 2024-03-20 09:51:25.119 oauthlib.oauth2.rfc6749.grant_types.base base:172] Validating access to scopes ['access:servers!server=82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25', 'read:users:groups!user', 'read:users:name!user'] for client 'jupyterhub-user-82c20951-9587-4691-812d-7cd49b1a42f5-orionhubnsuser.0.25' (<OAuthClient(identifier='jupyterhub-user-82c20951-9587-4691-812d-7cd49b1a42f5-orionhubnsuser.0.25')>).
[D 2024-03-20 09:51:25.120 JupyterHub provider:622] Allowing request for scope(s) for jupyterhub-user-82c20951-9587-4691-812d-7cd49b1a42f5-orionhubnsuser.0.25:  read:users:groups!user,read:users:name!user,access:servers!server=82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25
[D 2024-03-20 09:51:25.122 JupyterHub auth:320] Skipping oauth confirmation for <User(82c20951-9587-4691-812d-7cd49b1a42f5 1/2 running)> accessing Server at /user/82c20951-9587-4691-812d-7cd49b1a42f5/orionhubnsuser.0.25/

These when fails:

[I 2024-03-20 10:30:25.295 JupyterHub authenticator:137] Header name: Authorization 
    QueryParam name: token 
    Header is auth: True 
    Header content:  
    Cookie content:  
    Hub cookie content: 2|1:0|10:1710929630|20:jupyterhub-hub-login|44:NzE5YTc3Mjc0OGY5NDkwZjkwZjg5Y2JlNzNjZjExYmE=|799c78956dec761f2b2010e8b704abbdf753c7f29bd7873dcad793c2c22aea3a 
    Secret: secret-secret-secret 
    Username claim field: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier 
    Audience:  
    Token param: TOKEN
[I 2024-03-20 10:30:25.295 JupyterHub authenticator:76] Decoded token = TOKEN
[I 2024-03-20 10:30:25.297 JupyterHub authenticator:88] Username from claims: 70c3dbcc-a4e6-ee11-8010-186024ed73bd. User for set cookie: <User(70c3dbcc-a4e6-ee11-8010-186024ed73bd 1/2 running)>
[I 2024-03-20 10:30:25.297 JupyterHub authenticator:95] User cookie cleared: 1cefdbe5-a1e6-ee11-8010-186024ed73bd
[I 2024-03-20 10:30:25.298 JupyterHub authenticator:99] Request cookies: Set-Cookie: .AspNetCore.Cookies=COOKIE
    Set-Cookie: IsLoggedOrionUser=True
    Set-Cookie: _xsrf=2|0ad9a02b|bdea3d7f6f852b65541a3a9e995d9025|1710501277
    Set-Cookie: jupyterhub-hub-login="2|1:0|10:1710929630|20:jupyterhub-hub-login|44:NzE5YTc3Mjc0OGY5NDkwZjkwZjg5Y2JlNzNjZjExYmE=|799c78956dec761f2b2010e8b704abbdf753c7f29bd7873dcad793c2c22aea34a"
    Set-Cookie: jupyterhub-session-id=b5b4aba54a7e4156b76cd69e07958fed
[W 2024-03-20 10:30:25.298 JupyterHub base:701] Redirecting /hub/login?token=TOKEN=&next=/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process%20Tree%20-%20New%20Document%201831.ipynb to /hub/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process Tree - New Document 1831.ipynb. For sharing public links, use /user-redirect/
[I 2024-03-20 10:30:25.298 JupyterHub authenticator:103] Redirect authentication to URL=/hub/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process Tree - New Document 1831.ipynb?token=TOKEN
[I 2024-03-20 10:30:25.299 JupyterHub log:191] 302 GET /hub/login?token=[secret]&next=/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process%20Tree%20-%20New%20Document%201831.ipynb -> /hub/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process Tree - New Document 1831.ipynb?token=[secret] (1cefdbe5-a1e6-ee11-8010-186024ed73bd@192.168.65.4) 14.86ms
[I 2024-03-20 10:30:25.307 JupyterHub log:191] 302 GET /hub/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process%20Tree%20-%20New%20Document%201831.ipynb?token=[secret] -> /hub/login?next=%2Fhub%2Fuser%2F70c3dbcc-a4e6-ee11-8010-186024ed73bd%2Forionhubnsrigel.0.25%2Fnotebooks%2Fdocument%2F5497%2FProcess%2520Tree%2520-%2520New%2520Document%25201831.ipynb%3Ftoken%3TOKEN (@192.168.65.4) 1.37ms
[E 2024-03-20 10:30:25.312 JupyterHub authenticator:28] {'next': [b'/hub/user/70c3dbcc-a4e6-ee11-8010-186024ed73bd/orionhubnsrigel.0.25/notebooks/document/5497/Process%20Tree%20-%20New%20Document%201831.ipynb?token=TOKEN']}
[I 2024-03-20 10:30:25.312 JupyterHub authenticator:137] Header name: Authorization 
    QueryParam name: token 
    Header is auth: True 
    Header content:  
    Cookie content:  
    Hub cookie content:  
    Secret: secret-secret-secret 
    Username claim field: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier 
    Audience:  
    Token param: False
[E 2024-03-20 10:30:25.312 JupyterHub authenticator:67] There is no available info to authenticate
[D 2024-03-20 10:30:25.313 JupyterHub base:1371] No template for 401

I think that the cookie that I set is not present in the current request that I’m validating on the login.
What I expect would be to the auth working as in the first request for the other user.

I’m not quite sure I follow the full sequence of requests and credentials, but one thing you may be hung up on is the fact that set_login_cookie doesn’t have an effect if a login cookie is already set. Your clear_cookie does have an effect, but doesn’t change the result of the check for the request’s cookie within the current request. Looking at the code in JupyterHub, I’m not sure that’s the right thing to do - it doesn’t check if the user changed, only if it was defined already.

PR to fix the skipped cookie override: set login cookie if user changed by minrk · Pull Request #4739 · jupyterhub/jupyterhub · GitHub

If your looking for a workaround, try adding after set_login_user:

if jupyterhub_cookie:
    # set_login_cookie doesn't override existing cookie
    self.set_hub_cookie(user)

set_hub_cookie is the actual call to set the cookie, which is conditional on there not being a cookie set already.

Thanks, Minrk; that makes the trick to avoid the error with the cookie.