ANProto over ATProto -- using Bluesky PDSes to store ANProto messages and blobs

Architecture#

This reference app follows a standard server-side OAuth flow suited for a backend (Node.js/Express) application.

Components#

  1. Express Server (src/index.ts)

    • Host web endpoints (/, /login, /oauth/callback).
    • Manages the browser session (cookie-based) using iron-session.
    • NOTE: The browser session is separate from the OAuth session. The browser session just remembers "Who is logged in here?" (by storing the DID).
  2. OAuth Client (src/client.ts)

    • Instance of NodeOAuthClient.
    • Manages the complexity of the handshake, token exchanges, and key management (DPoP).
    • Uses client-metadata to define itself to the world (redirect URIs, etc.).
  3. Storage Adapters (src/storage.ts)

    • State Store: Temporarily stores the random state parameter generated during the login request to prevent CSRF.
    • Session Store: Persists the actual Access and Refresh tokens (the "OAuth Session") mapped to the user's DID.
  4. Database (src/db.ts)

    • A simple SQLite database to back the Storage Adapters.
    • In a real app, this would be Postgres, Redis, etc.

The Flow#

  1. Initiation:
    • User enters Handle.
    • App calls client.authorize(handle).
    • App redirects User to the PDS (e.g., bsky.social login page).
  2. Authentication:
    • User logs in at the PDS.
    • User approves the app.
  3. Callback:
    • PDS redirects User back to /oauth/callback?code=....
    • App calls client.callback(params).
    • client exchanges code for tokens.
    • client saves tokens to SessionStore.
    • App saves session.did to the browser cookie.
  4. Usage:
    • On subsequent requests, App reads DID from browser cookie.
    • App loads OAuth tokens from SessionStore using the DID.
    • App creates an Agent to make API calls.