···11-# CLAUDE.md
22-33-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44-55-## Project Overview
66-77-A Deno-compatible AT Protocol OAuth client built specifically for handle-based authentication. This is **NOT a drop-in replacement** for `@atproto/oauth-client-node` - it's an opinionated, handle-focused alternative built on Web Crypto API.
88-99-**Key Design Decisions:**
1010-1111-- Handle-only inputs (e.g., `alice.bsky.social`) - no DIDs or URLs accepted
1212-- Slingshot resolver as default with fallbacks (Bluesky API → direct resolution)
1313-- Web Crypto API exclusively for cross-platform compatibility
1414-- Built for Deno runtime, not Node.js
1515-1616-## Commands
1717-1818-### Development
1919-2020-```bash
2121-# Type checking
2222-deno task check
2323-2424-# Format code
2525-deno task fmt # Check formatting
2626-deno task fmt:fix # Auto-fix formatting
2727-2828-# Linting
2929-deno task lint
3030-3131-# Run all checks (CI simulation)
3232-deno task ci
3333-```
3434-3535-### Testing
3636-3737-```bash
3838-# Run all tests
3939-deno task test
4040-4141-# Run specific test file
4242-deno test tests/session_test.ts --allow-net --allow-read
4343-4444-# Run tests with coverage
4545-deno test --coverage=coverage --allow-net --allow-read
4646-```
4747-4848-### Publishing
4949-5050-```bash
5151-# Publish to JSR (requires proper version in deno.json)
5252-deno publish
5353-```
5454-5555-## Architecture
5656-5757-### Core Flow: OAuth Authorization
5858-5959-1. **Handle Resolution** (`src/resolvers.ts`)
6060- - `SlingshotResolver` (default): Uses Slingshot's `resolveMiniDoc` endpoint for fast DID+PDS lookup
6161- - Fallback chain: Slingshot standard → Bluesky API → Direct `.well-known/atproto-did` lookup
6262- - `DirectoryResolver`: Bluesky API only (no Slingshot)
6363- - `CustomResolver`: User-provided resolution logic
6464-6565-2. **OAuth Endpoint Discovery** (`src/resolvers.ts`)
6666- - Discover auth server from PDS: `/.well-known/oauth-protected-resource`
6767- - Discover OAuth endpoints from auth server: `/.well-known/oauth-authorization-server`
6868- - Fallback: Try PDS directly if auth server discovery fails
6969-7070-3. **Authorization Flow** (`src/client.ts`)
7171- - Generate PKCE parameters (code_verifier, code_challenge)
7272- - Store PKCE data in storage with 10-minute TTL (`pkce:{state}`)
7373- - Push Authorization Request (PAR) to get request_uri
7474- - Return authorization URL for user redirect
7575-7676-4. **Token Exchange** (`src/client.ts`)
7777- - Validate state parameter and retrieve PKCE data
7878- - Generate DPoP ES256 key pair (Web Crypto API)
7979- - Exchange authorization code for tokens with DPoP proof
8080- - Handle DPoP nonce challenges (retry with nonce on 400 status)
8181- - Create and return authenticated session
8282-8383-5. **DPoP Authentication** (`src/dpop.ts`)
8484- - ES256 (ECDSA P-256) key generation using Web Crypto API
8585- - JWT creation with `jsr:@panva/jose` (NOT npm:jose)
8686- - DPoP proof includes: jti, htm, htu, iat, exp, optional ath (access token hash), optional nonce
8787- - Automatic nonce handling: retry on 401 with `DPoP-Nonce` header
8888-8989-### Key Components
9090-9191-**`OAuthClient` (src/client.ts)**
9292-9393-- Main entry point for OAuth operations
9494-- Methods: `authorize()`, `callback()`, `store()`, `restore()`, `refresh()`, `signOut()`
9595-- Manages PKCE flow, token exchange, and session lifecycle
9696-9797-**`Session` (src/session.ts)**
9898-9999-- Represents authenticated user session
100100-- Properties: `did`, `handle`, `pdsUrl`, `accessToken`, `refreshToken`, `isExpired`
101101-- `makeRequest()`: Makes DPoP-authenticated HTTP requests with automatic nonce handling
102102-- Serializable via `toJSON()` / `fromJSON()` for storage
103103-104104-**Storage Implementations (src/storage.ts)**
105105-106106-- `MemoryStorage`: In-memory with TTL support (development/testing)
107107-- `SQLiteStorage`: Example SQLite backend (reference implementation)
108108-- `LocalStorage`: Browser localStorage wrapper
109109-- All implement `OAuthStorage` interface: `get()`, `set()`, `delete()`
110110-111111-**Error Hierarchy (src/errors.ts)**
112112-113113-- Base: `OAuthError` (all OAuth errors inherit from this)
114114-- Handle errors: `InvalidHandleError`, `HandleResolutionError`
115115-- Discovery errors: `PDSDiscoveryError`, `AuthServerDiscoveryError`
116116-- Flow errors: `TokenExchangeError`, `AuthorizationError`, `InvalidStateError`
117117-- Auth errors: `DPoPError`, `SessionError`
118118-119119-### Critical Implementation Details
120120-121121-**Web Crypto API vs Node.js crypto**
122122-123123-- MUST use `crypto.subtle.generateKey()` with explicit `namedCurve: "P-256"`
124124-- MUST use `jsr:@panva/jose` NOT `npm:jose` or Node.js jose packages
125125-- DPoP key generation MUST set `extractable: true` for JWK export
126126-- Private key import MUST clean JWK (remove conflicting `key_ops` from exportJWK)
127127-128128-**DPoP Nonce Handling**
129129-130130-- AT Protocol uses 400 status (not 401) for initial nonce challenges during token exchange
131131-- Token refresh uses 401 status for nonce challenges
132132-- Always check `DPoP-Nonce` header and retry with nonce if present
133133-- Nonce included in JWT payload, not header
134134-135135-**Handle Resolution Strategy**
136136-137137-- Default: Slingshot with multi-level fallbacks
138138-- `resolveMiniDoc` returns both DID + PDS in one request (preferred)
139139-- Standard resolution requires two requests: handle→DID, then DID document→PDS
140140-- PDS URL extracted from DID document's `AtprotoPersonalDataServer` service
141141-142142-**Session Storage Pattern**
143143-144144-- PKCE data stored with `pkce:{state}` prefix, 10-minute TTL
145145-- Sessions stored with `session:{sessionId}` prefix
146146-- Auto-refresh on restore if token expires within 5 minutes
147147-- `isExpired` uses 5-minute buffer to prevent edge cases
148148-149149-## Testing Patterns
150150-151151-Tests use Deno's built-in test framework with the following patterns:
152152-153153-**Mock/Fake Pattern** (per user's global CLAUDE.md)
154154-155155-- Tests must NOT rely on external services
156156-- Use injection patterns for all dependencies
157157-- Mock storage, resolvers, and network calls in tests
158158-- Test files: `tests/*_test.ts`
159159-160160-**Test File Structure**
161161-162162-- `errors_test.ts`: Error class behavior and messages
163163-- `session_test.ts`: Session management, token refresh, serialization
164164-- `storage_test.ts`: Storage implementations with TTL
165165-- `utils_test.ts`: Utility functions (PKCE, DPoP, etc.)
166166-167167-## Security & OAuth Best Practices
168168-169169-**CRITICAL: No OAuth Workarounds** (per user's global CLAUDE.md)
170170-171171-- Always follow OAuth 2.0, AT Protocol, and DPoP specs exactly
172172-- No shortcuts or "good enough" solutions for auth flows
173173-- Properly validate state parameters (CSRF protection)
174174-- Use secure PKCE (S256, not plain)
175175-- DPoP proof must include all required claims
176176-177177-**Token Management**
178178-179179-- Store refresh tokens securely in storage backend
180180-- Never log tokens or sensitive cryptographic material
181181-- Clean up PKCE data after use (success or failure)
182182-- Revoke tokens on sign out (best effort)
183183-184184-## Common Development Patterns
185185-186186-**Adding a new storage backend:**
187187-188188-1. Implement `OAuthStorage` interface from `src/types.ts`
189189-2. Implement `get<T>()`, `set<T>()`, `delete()` with TTL support
190190-3. Handle TTL expiration in `get()` (return null if expired)
191191-4. Add tests following pattern in `tests/storage_test.ts`
192192-193193-**Adding a new resolver:**
194194-195195-1. Implement `HandleResolver` interface from `src/types.ts`
196196-2. Implement `resolve(handle)` returning `{ did: string; pdsUrl: string }`
197197-3. Throw `HandleResolutionError` on failure
198198-4. Consider fallback mechanisms like `SlingshotResolver`
199199-200200-**Error handling:**
201201-202202-- Catch and re-throw with appropriate error class
203203-- Preserve error cause chain for debugging
204204-- All OAuth errors extend `OAuthError` base class
205205-- Use specific error types for different failure modes
206206-207207-## Important Constraints
208208-209209-- **Handle-only inputs**: Client only accepts AT Protocol handles, not DIDs or URLs
210210-- **Deno runtime**: Built for Deno, uses Web Standards APIs exclusively
211211-- **No Node.js crypto**: Cannot use Node.js crypto modules (incompatible with Deno)
212212-- **Slingshot dependency**: Default resolver uses third-party Slingshot service (can be configured)
213213-- **ES256 only**: DPoP uses ECDSA P-256 (ES256), not RS256 or other algorithms