Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AzureV2 authentication does not successfully save access_token as a secure cookie when encryption is enabled #4161

Open
minasouliman opened this issue Nov 29, 2022 · 3 comments
Milestone

Comments

@minasouliman
Copy link
Contributor

ALL software version info

panel == 0.14.1
python == 3.9.13
bokeh == 2.4.3

Description of expected behavior and the observed behavior

If I set up oauth using the AzureV2 provider and enable encryption, setting the access_token cookie fails as the token size is larger than the 4096 bytes max limit. The access_token I get back from azure is ~2,200 bytes. After encrypting it, it becomes ~3,100 and then after tornado is done signing and b64 encoding it, it jumps to ~4,300 bytes. At this point, it just silently fails to set the cookie, and future calls to pn.state.access_token return None as they should.

This is a non-issue when using the Azure (V1) provider and is also a non-issue on either if you disable encryption.

Complete, minimal, self-contained example code that reproduces the issue

panel_app.py

import panel as pn
print(pn.state.access_token)

run using all the appropriate panel cli args to enable authentication.

Screenshots or screencasts of the bug in action

image

Proposed solution

I tried to look up any documentation about expected max length for the access_token and couldn't find anything helpful, and a couple of links suggesting that there is no max length. My current workaround is to break up the token into it's header/paylod/signature and encrypt and store each separately. The modifications are--

Add a specific _on_auth() to the AzureAdV2LoginHandler class

    def _on_auth(self, id_token, access_token):
        decoded = decode_id_token(id_token)
        user_key = config.oauth_jwt_user or self._USER_KEY
        if user_key in decoded:
            user = decoded[user_key]
        else:
            log.error("%s token payload did not contain expected %r.",
                      type(self).__name__, user_key)
            raise HTTPError(400, "OAuth token payload missing user information")
        self.set_secure_cookie('user', user, expires_days=config.oauth_expiry)
        if state.encryption:
            header,payload,sig = access_token.split('.')
            header = state.encryption.encrypt(header.encode('utf-8'))
            payload = state.encryption.encrypt(payload.encode('utf-8'))
            sig = state.encryption.encrypt(sig.encode('utf-8'))
            
            self.set_secure_cookie('access_token_header', header, expires_days=config.oauth_expiry)
            self.set_secure_cookie('access_token_payload', payload, expires_days=config.oauth_expiry)
            self.set_secure_cookie('access_token_sig', sig, expires_days=config.oauth_expiry)

            id_token = state.encryption.encrypt(id_token.encode('utf-8'))
            self.set_secure_cookie('id_token', id_token, expires_days=config.oauth_expiry)
            return user

        
        self.set_secure_cookie('access_token', access_token, expires_days=config.oauth_expiry)
        self.set_secure_cookie('id_token', id_token, expires_days=config.oauth_expiry)
        return user

and modify the access_token property in _state to--

   @property
    def access_token(self) -> str | None:
        from ..config import config
        from tornado.web import decode_signed_value

        access_token = self.cookies.get('access_token')
        access_token_header = self.cookies.get('access_token_header')
        access_token_payload = self.cookies.get('access_token_payload')
        access_token_sig = self.cookies.get('access_token_sig')
        if (access_token is None and access_token_header is None):
            return None

        if access_token_header is not None:
            access_token_header = decode_signed_value(config.cookie_secret, 'access_token_header', access_token_header)
            access_token_payload = decode_signed_value(config.cookie_secret, 'access_token_payload', access_token_payload)
            access_token_sig = decode_signed_value(config.cookie_secret, 'access_token_sig', access_token_sig)
            access_token_header = self.encryption.decrypt(access_token_header).decode('utf-8')
            access_token_payload = self.encryption.decrypt(access_token_payload).decode('utf-8')
            access_token_sig = self.encryption.decrypt(access_token_sig).decode('utf-8')
            return access_token_header + '.' + access_token_payload + '.' + access_token_sig

        access_token = decode_signed_value(config.cookie_secret, 'access_token', access_token)
        if self.encryption is None:
            return access_token.decode('utf-8')
        return self.encryption.decrypt(access_token).decode('utf-8')

code seems fairly clunky, so lmk if anyone has any suggestions on making it more concise, or if there is any other way that you can think of getting around this. Happy to submit a PR if you're good with the mods above.

Thanks!

@kghildyal
Copy link

Any update on this? I am facing a same issue

@philippjfr philippjfr added this to the v1.6.0 milestone Jan 16, 2025
@philippjfr
Copy link
Member

@kghildyal I'll take a quick look now. Thanks for the detailed writeup @minasouliman.

@philippjfr
Copy link
Member

Apparently most modern browsers still have hard max cookie sizes of about 4096 bytes. The only thing I can suggest we try is to apply some basic compression.

@philippjfr philippjfr modified the milestones: v1.6.0, v1.6.1 Jan 25, 2025
@philippjfr philippjfr modified the milestones: v1.6.1, v1.6.2 Feb 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants