an atproto pds written in F# (.NET 9) 🦒
pds
fsharp
giraffe
dotnet
atproto
1# AT Protocol Session & Account Authentication
2
3## Session Authentication (Legacy Bearer JWT)
4
5Based on the [XRPC Spec](https://atproto.com/specs/xrpc#authentication):
6
7### Token Types
8
9| Token | JWT `typ` Header | Lifetime | Purpose |
10| ------------- | ---------------- | --------------------------- | ------------------------------ |
11| Access Token | `at+jwt` | Short (~2min refresh cycle) | Authenticate most API requests |
12| Refresh Token | `refresh+jwt` | Longer (~2 months) | Obtain new access tokens |
13
14### Endpoints
15
16- **`createSession`**: Login with identifier (handle/email) + password → returns `{accessJwt, refreshJwt, handle, did}`
17- **`refreshSession`**: Uses refresh JWT in Bearer header → returns new `{accessJwt, refreshJwt, handle, did}`
18- **`createAccount`**: Register new account → returns session tokens + creates DID
19
20### JWT Claims (Server-Generated)
21
22Servers should implement **domain separation** using the `typ` header field:
23
24- Access: `typ: at+jwt` (per [RFC 9068](https://www.rfc-editor.org/rfc/rfc9068.html))
25- Refresh: `typ: refresh+jwt`
26
27Standard JWT claims: `sub` (DID), `iat`, `exp`, `jti` (nonce)
28
29### Configuration Required
30
31Yes, JWT signing requires a **secret key** for HMAC-SHA256 (HS256). This should be:
32
33- Loaded from configuration/environment variable (e.g., `PDS_JWT_SECRET`)
34- At least 32 bytes of cryptographically random data
35- Never hardcoded or committed to source control
36
37## Account Storage
38
39### Reference PDS Approach
40
41The Bluesky reference PDS uses:
42
43- **SQLite database per user** (recent architecture)
44- `account.sqlite` contains: handle, email, DID, password hash
45- Accounts indexed by DID (primary) and handle (unique)
46
47## App Passwords
48
49App passwords are a security feature allowing restricted access:
50
51- Format: `xxxx-xxxx-xxxx-xxxx`
52- Created/revoked independently from main password
53- Grants limited permissions (no auth settings changes)
54
55## Inter-Service Auth (Different from Session Auth)
56
57For service-to-service requests, different mechanism:
58
59- Uses **asymmetric signing** (ES256/ES256K) with account's signing key
60- Short-lived tokens (~60sec)
61- Validated against DID document
62
63## Summary: Implementation Decisions
64
65| Aspect | Decision | Rationale |
66| --------------- | -------------------------- | ------------------------------------ |
67| Token signing | HS256 (symmetric) | Simpler, standard for session tokens |
68| Secret storage | Config/env var | Required for security |
69| Account storage | In-memory (initial) | Matches existing patterns |
70| Password hash | SHA-256 + salt | Uses existing Crypto.fs |
71| Token lifetimes | Access: 15min, Refresh: 7d | Conservative defaults |
72
73## References
74
75- [XRPC Authentication Spec](https://atproto.com/specs/xrpc#authentication)
76- [RFC 9068 - JWT Access Tokens](https://www.rfc-editor.org/rfc/rfc9068.html)
77- [Bluesky PDS GitHub](https://github.com/bluesky-social/pds)