Blame

261081 Claude (MCP) 2026-03-15 06:17:07
[mcp] V3 ATProto Auth implementation summary
1
---
2
category: reference
3
tags:
4
- v3
5
- auth
6
- atproto
7
- oauth
8
last_updated: 2026-03-15
9
---
10
11
# V3: ATProto OAuth Production Auth Service
12
13
**Branch:** `feat/v3-atproto-auth`
14
**Commit:** `4580bbf`
15
**Status:** Implementation complete, 24/24 tests pass (79/79 total)
16
17
## What Changed
18
19
Replaced the stub auth server (`app/auth_server.py`) with a production ATProto OAuth flow adapted from the VS-1 spike (`spike/atproto-oauth/`).
20
21
## Deliverables
22
23
| File | Description |
24
|------|-------------|
25
| `app/auth/atproto_oauth.py` | PAR, DPoP, token exchange, refresh, revocation |
26
| `app/auth/atproto_identity.py` | Handle/DID resolution (DNS TXT + HTTP well-known) |
27
| `app/auth/atproto_security.py` | SSRF mitigations, hardened HTTP client |
28
| `app/auth_server.py` | Full Flask app with `create_app()` factory |
29
| `app/auth/templates/` | login.html, signup.html, error.html, base.html (Pico CSS) |
30
| `tests/test_auth_server.py` | 24 tests covering all routes and flows |
31
| `requirements.txt` | Added authlib>=1.3, dnspython>=2.6, requests-hardened>=1.0.0b3 |
32
| `ansible/roles/database/files/schema.sql` | Added `oauth_auth_requests` table, updated `oauth_sessions` |
33
| `ansible/roles/deploy/files/robot-auth.service` | Added EnvironmentFile, CLIENT_JWK_PATH, ROBOT_DB_PATH |
34
35
## Routes
36
37
| Method | Path | Purpose |
38
|--------|------|---------|
39
| GET | `/auth/client-metadata.json` | ATProto client metadata (client_id URL) |
40
| GET | `/auth/login` | Login page with handle input |
41
| POST | `/auth/login` | Initiate OAuth: resolve identity, PAR, redirect to AS |
42
| GET | `/auth/callback` | Exchange code for tokens, issue platform JWT cookie |
43
| GET | `/auth/signup` | Username form (first-time users) |
44
| POST | `/auth/signup` | Create user record, issue JWT cookie |
45
| GET | `/auth/logout` | Revoke ATProto tokens, clear cookie |
46
| GET | `/.well-known/oauth-authorization-server` | AS metadata stub |
47
| GET | `/.well-known/jwks.json` | RS256 public key |
48
49
## Key Design Decisions
50
51
- **Platform JWT != ATProto tokens**: OAuth callback issues an RS256 platform JWT cookie; ATProto tokens are stored in `oauth_sessions` for potential future use
52
- **Cookie**: `platform_token` on `.robot.wtf` domain, HttpOnly, Secure, SameSite=Lax, 24h TTL
53
- **Signup flow**: First-time users (no `users` row for their DID) get redirected to `/auth/signup` to choose a username; default derived from handle prefix
54
- **DPoP nonce handling**: Preserved from spike -- automatic retry on `use_dpop_nonce` error
55
- **client_id**: `https://robot.wtf/auth/client-metadata.json` (stable URL)
56
- **Scope**: `atproto` (identity-only, no repo access)
57
58
## Schema Changes
59
60
Added `oauth_auth_requests` table for in-flight OAuth state (temporary, deleted after callback). Updated `oauth_sessions` to use DID as primary key with ATProto-specific fields (authserver_iss, pds_url, dpop_authserver_nonce, dpop_private_jwk).
61
62
## Test Coverage
63
64
24 tests covering: client metadata structure, login page rendering, invalid input handling, OAuth callback (error, missing params, unknown state, returning user, new user), signup flow (no session, form rendering, user creation, invalid/reserved/duplicate username rejection), logout cookie clearing, JWKS endpoint, AS metadata, and default username derivation.