about things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 109 lines 3.5 kB view raw view rendered
1# auth 2 3atproto uses OAuth 2.0 for application authorization. 4 5## the flow 6 71. user visits application 82. application redirects to user's PDS for authorization 93. user approves requested scopes 104. PDS redirects back with authorization code 115. application exchanges code for tokens 126. application uses tokens to act on user's behalf 13 14standard OAuth, but the authorization server is the user's PDS, not a central service. 15 16## scopes 17 18scopes define what an application can do: 19 20``` 21atproto # full access (legacy) 22repo:fm.plyr.track # read/write fm.plyr.track collection 23repo:fm.plyr.like # read/write fm.plyr.like collection 24repo:read # read-only access to repo 25``` 26 27granular scopes let users grant minimal permissions. an app that only needs to read your profile shouldn't have write access to your posts. 28 29## permission sets 30 31listing individual scopes is noisy. permission sets bundle them under human-readable names: 32 33``` 34include:fm.plyr.authFullApp # "plyr.fm Music Library" 35``` 36 37instead of seeing `fm.plyr.track, fm.plyr.like, fm.plyr.comment, ...`, users see a single permission with a description. 38 39permission sets are lexicons published to `com.atproto.lexicon.schema` on your authority repo. 40 41from [plyr.fm permission sets](https://github.com/zzstoatzz/plyr.fm/blob/main/docs/lexicons/overview.md#permission-sets) 42 43## session management 44 45tokens expire. applications need refresh logic: 46 47```python 48class SessionManager: 49 def __init__(self, session_path: Path): 50 self.session_path = session_path 51 self._client: AsyncClient | None = None 52 53 async def get_client(self) -> AsyncClient: 54 if self._client: 55 return self._client 56 57 # try loading saved session 58 if self.session_path.exists(): 59 session_str = self.session_path.read_text() 60 self._client = AsyncClient() 61 await self._client.login(session_string=session_str) 62 self._client.on_session_change(self._save_session) 63 return self._client 64 65 # fall back to fresh login 66 self._client = AsyncClient() 67 await self._client.login(handle, password) 68 self._save_session(None, None) 69 return self._client 70 71 def _save_session(self, event, session): 72 self.session_path.write_text(self._client.export_session_string()) 73``` 74 75from [bot](https://github.com/zzstoatzz/bot) - persists sessions to disk, refreshes automatically. 76 77## per-request credentials 78 79for multi-tenant applications (one backend serving many users), credentials come per-request: 80 81```python 82# middleware extracts from headers 83x-atproto-handle: user.handle 84x-atproto-password: app-password 85 86# or from OAuth session 87authorization: Bearer <token> 88``` 89 90from [pdsx MCP server](https://github.com/zzstoatzz/pdsx) - accepts credentials via HTTP headers for multi-tenant deployment. 91 92## app passwords 93 94for bots and automated tools, app passwords are simpler than full OAuth: 95 961. user creates app password in their PDS settings 972. bot uses handle + app password to authenticate 983. no redirect flow needed 99 100app passwords have full account access. use OAuth with scopes when you need granular permissions. 101 102## why this matters 103 104OAuth at the protocol level means: 105 106- users authorize apps, not the other way around 107- applications can't lock in users by controlling auth 108- the same identity works across all atmospheric applications 109- granular scopes enable minimal-permission applications