-
-
Notifications
You must be signed in to change notification settings - Fork 76
Add OAuth session support via OAuthSession mixin #640
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
base: main
Are you sure you want to change the base?
Conversation
incorporates the best aspects of PR MarshalX#640's mixin approach while keeping our async support, security module, and comprehensive test coverage. **key changes:** - add OAuthSessionMixin and AsyncOAuthSessionMixin to atproto_client - integrate mixins into Client and AsyncClient classes - override _invoke to transparently handle OAuth sessions with DPoP - add oauth_login(), oauth_logout(), export_oauth_session() methods - update codegen to auto-transform sync mixin to async **usage:** ```python from atproto_client import Client from atproto_oauth import OAuthClient # get OAuth session via OAuthClient (unchanged) oauth_client = OAuthClient(...) session = await oauth_client.handle_callback(code, state, iss) # use with Client - requests are now transparently authenticated client = Client( oauth_client_id='https://myapp.com/client-metadata.json', oauth_redirect_uri='https://myapp.com/callback', oauth_scope='atproto', ) client.oauth_login(session) # all existing methods work with OAuth session timeline = client.get_timeline() ``` **what this enables:** - seamless integration: existing client methods work with OAuth sessions - no manual DPoP proof generation for each request - automatic nonce rotation on PDS requests - session export/import for persistence 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
incorporates the best aspects of PR MarshalX#640's mixin approach while keeping our async support, security module, and comprehensive test coverage. **key changes:** - add OAuthSessionMixin and AsyncOAuthSessionMixin to atproto_client - integrate mixins into Client and AsyncClient classes - override _invoke to transparently handle OAuth sessions with DPoP - add oauth_login(), oauth_logout(), export_oauth_session() methods - update codegen to auto-transform sync mixin to async - **remove make_authenticated_request()** - now handled by mixin's _invoke **usage:** ```python from atproto_client import AsyncClient from atproto_oauth import OAuthClient # get OAuth session via OAuthClient (unchanged) oauth_client = OAuthClient(...) session = await oauth_client.handle_callback(code, state, iss) # use with Client - requests are now transparently authenticated client = AsyncClient( oauth_client_id='https://myapp.com/client-metadata.json', oauth_redirect_uri='https://myapp.com/callback', oauth_scope='atproto', ) client.oauth_login(session) # all existing methods work with OAuth session timeline = await client.get_timeline() ``` **what this enables:** - seamless integration: existing client methods work with OAuth sessions - no manual DPoP proof generation for each request - automatic nonce rotation on PDS requests - session export/import for persistence 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
incorporates the best aspects of PR MarshalX#640's mixin approach while keeping our async support, security module, and comprehensive test coverage. **key changes:** - add OAuthSessionMixin and AsyncOAuthSessionMixin to atproto_client - integrate mixins into Client and AsyncClient classes - override _invoke to transparently handle OAuth sessions with DPoP - add oauth_login(), oauth_logout(), export_oauth_session() methods - update codegen to auto-transform sync mixin to async - **remove make_authenticated_request()** - now handled by mixin's _invoke **usage:** ```python from atproto_client import AsyncClient from atproto_oauth import OAuthClient # get OAuth session via OAuthClient (unchanged) oauth_client = OAuthClient(...) session = await oauth_client.handle_callback(code, state, iss) # use with Client - requests are now transparently authenticated client = AsyncClient( oauth_client_id='https://myapp.com/client-metadata.json', oauth_redirect_uri='https://myapp.com/callback', oauth_scope='atproto', ) client.oauth_login(session) # all existing methods work with OAuth session timeline = await client.get_timeline() ``` **what this enables:** - seamless integration: existing client methods work with OAuth sessions - no manual DPoP proof generation for each request - automatic nonce rotation on PDS requests - session export/import for persistence 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
incorporates the best aspects of PR MarshalX#640's mixin approach while keeping our async support, security module, and comprehensive test coverage. **key changes:** - add OAuthSessionMixin and AsyncOAuthSessionMixin to atproto_client - integrate mixins into Client and AsyncClient classes - override _invoke to transparently handle OAuth sessions with DPoP - add oauth_login(), oauth_logout(), export_oauth_session() methods - update codegen to auto-transform sync mixin to async - **remove make_authenticated_request()** - now handled by mixin's _invoke - **remove SessionStore** - caller now manages session persistence - **remove MemorySessionStore** - no longer needed - simplify OAuthClient to only require StateStore **usage:** ```python from atproto_client import AsyncClient from atproto_oauth import OAuthClient from atproto_oauth.stores import MemoryStateStore # OAuth client only needs state store now oauth = OAuthClient( client_id='https://myapp.com/client-metadata.json', redirect_uri='https://myapp.com/callback', scope='atproto', state_store=MemoryStateStore(), ) # get session - caller persists it however they want session = await oauth.handle_callback(code, state, iss) await save_to_database(session) # your responsibility # use with Client client = AsyncClient( oauth_client_id='https://myapp.com/client-metadata.json', oauth_redirect_uri='https://myapp.com/callback', oauth_scope='atproto', ) client.oauth_login(session) # all existing methods work with OAuth session timeline = await client.get_timeline() ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
Hey @zzstoatzz and @krystofyah! Thank you for working on it. Could you please help me to understand on which PR should I look for as the main one?
Also, I do see in attached PR by @zzstoatzz that mixin approach does not fit well into 3d parties projects. Having |
|
in my attempted adoption of the mixin pattern it appeared to cause complexity, iirc, it seemed like the mixin pattern might be more complex for per-request client situations as opposed to single clients. to be fair that could have been for reasons idiosyncratic to the use case. will do a little more digging and clean up my PR when time allows, happy to coordinate / merge efforts with @krystofyah if that works out! |

Summary
An alternative approach to adding OAuth to the python client largely based on @zzstoatzz's https://github.com/zzstoatzz/atproto OAuth implementation fork but with a few main differences:
OAuthSessionmixin to add oauth methods to theatproto_client.Clientstoresfrom theClient. This approach requires storing sessions to be handled outside of the clientinvokemethod to make authenticated requests so that all namespace methods still work as they do for regular sessions. For example you can make calls likeclient.getPostsor client.app.bsky.feed.getFeedSkeleton` after establishing a session via oauthComponents
OauthSessionMethodsMixin- OAuth flow handling (PAR, token exchange, refresh)DPoPManager- DPoP proof generation and nonce managementPKCEManager- PKCE challenge/verifier generationOAuthSession- OAuth session state managementUsage
Testing