an atproto pds written in F# (.NET 9) 🦒
pds fsharp giraffe dotnet atproto

AT Protocol Session & Account Authentication#

Session Authentication (Legacy Bearer JWT)#

Based on the XRPC Spec:

Token Types#

Token JWT typ Header Lifetime Purpose
Access Token at+jwt Short (~2min refresh cycle) Authenticate most API requests
Refresh Token refresh+jwt Longer (~2 months) Obtain new access tokens

Endpoints#

  • createSession: Login with identifier (handle/email) + password → returns {accessJwt, refreshJwt, handle, did}
  • refreshSession: Uses refresh JWT in Bearer header → returns new {accessJwt, refreshJwt, handle, did}
  • createAccount: Register new account → returns session tokens + creates DID

JWT Claims (Server-Generated)#

Servers should implement domain separation using the typ header field:

  • Access: typ: at+jwt (per RFC 9068)
  • Refresh: typ: refresh+jwt

Standard JWT claims: sub (DID), iat, exp, jti (nonce)

Configuration Required#

Yes, JWT signing requires a secret key for HMAC-SHA256 (HS256). This should be:

  • Loaded from configuration/environment variable (e.g., PDS_JWT_SECRET)
  • At least 32 bytes of cryptographically random data
  • Never hardcoded or committed to source control

Account Storage#

Reference PDS Approach#

The Bluesky reference PDS uses:

  • SQLite database per user (recent architecture)
  • account.sqlite contains: handle, email, DID, password hash
  • Accounts indexed by DID (primary) and handle (unique)

App Passwords#

App passwords are a security feature allowing restricted access:

  • Format: xxxx-xxxx-xxxx-xxxx
  • Created/revoked independently from main password
  • Grants limited permissions (no auth settings changes)

Inter-Service Auth (Different from Session Auth)#

For service-to-service requests, different mechanism:

  • Uses asymmetric signing (ES256/ES256K) with account's signing key
  • Short-lived tokens (~60sec)
  • Validated against DID document

Summary: Implementation Decisions#

Aspect Decision Rationale
Token signing HS256 (symmetric) Simpler, standard for session tokens
Secret storage Config/env var Required for security
Account storage In-memory (initial) Matches existing patterns
Password hash SHA-256 + salt Uses existing Crypto.fs
Token lifetimes Access: 15min, Refresh: 7d Conservative defaults

References#