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