+194
.claude/CLAUDE.md
+194
.claude/CLAUDE.md
···
1
+
# CLAUDE.md
2
+
3
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+
## Project Overview
6
+
7
+
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.
8
+
9
+
**Key Design Decisions:**
10
+
- Handle-only inputs (e.g., `alice.bsky.social`) - no DIDs or URLs accepted
11
+
- Slingshot resolver as default with fallbacks (Bluesky API → direct resolution)
12
+
- Web Crypto API exclusively for cross-platform compatibility
13
+
- Built for Deno runtime, not Node.js
14
+
15
+
## Commands
16
+
17
+
### Development
18
+
```bash
19
+
# Type checking
20
+
deno task check
21
+
22
+
# Format code
23
+
deno task fmt # Check formatting
24
+
deno task fmt:fix # Auto-fix formatting
25
+
26
+
# Linting
27
+
deno task lint
28
+
29
+
# Run all checks (CI simulation)
30
+
deno task ci
31
+
```
32
+
33
+
### Testing
34
+
```bash
35
+
# Run all tests
36
+
deno task test
37
+
38
+
# Run specific test file
39
+
deno test tests/session_test.ts --allow-net --allow-read
40
+
41
+
# Run tests with coverage
42
+
deno test --coverage=coverage --allow-net --allow-read
43
+
```
44
+
45
+
### Publishing
46
+
```bash
47
+
# Publish to JSR (requires proper version in deno.json)
48
+
deno publish
49
+
```
50
+
51
+
## Architecture
52
+
53
+
### Core Flow: OAuth Authorization
54
+
55
+
1. **Handle Resolution** (`src/resolvers.ts`)
56
+
- `SlingshotResolver` (default): Uses Slingshot's `resolveMiniDoc` endpoint for fast DID+PDS lookup
57
+
- Fallback chain: Slingshot standard → Bluesky API → Direct `.well-known/atproto-did` lookup
58
+
- `DirectoryResolver`: Bluesky API only (no Slingshot)
59
+
- `CustomResolver`: User-provided resolution logic
60
+
61
+
2. **OAuth Endpoint Discovery** (`src/resolvers.ts`)
62
+
- Discover auth server from PDS: `/.well-known/oauth-protected-resource`
63
+
- Discover OAuth endpoints from auth server: `/.well-known/oauth-authorization-server`
64
+
- Fallback: Try PDS directly if auth server discovery fails
65
+
66
+
3. **Authorization Flow** (`src/client.ts`)
67
+
- Generate PKCE parameters (code_verifier, code_challenge)
68
+
- Store PKCE data in storage with 10-minute TTL (`pkce:{state}`)
69
+
- Push Authorization Request (PAR) to get request_uri
70
+
- Return authorization URL for user redirect
71
+
72
+
4. **Token Exchange** (`src/client.ts`)
73
+
- Validate state parameter and retrieve PKCE data
74
+
- Generate DPoP ES256 key pair (Web Crypto API)
75
+
- Exchange authorization code for tokens with DPoP proof
76
+
- Handle DPoP nonce challenges (retry with nonce on 400 status)
77
+
- Create and return authenticated session
78
+
79
+
5. **DPoP Authentication** (`src/dpop.ts`)
80
+
- ES256 (ECDSA P-256) key generation using Web Crypto API
81
+
- JWT creation with `jsr:@panva/jose` (NOT npm:jose)
82
+
- DPoP proof includes: jti, htm, htu, iat, exp, optional ath (access token hash), optional nonce
83
+
- Automatic nonce handling: retry on 401 with `DPoP-Nonce` header
84
+
85
+
### Key Components
86
+
87
+
**`OAuthClient` (src/client.ts)**
88
+
- Main entry point for OAuth operations
89
+
- Methods: `authorize()`, `callback()`, `store()`, `restore()`, `refresh()`, `signOut()`
90
+
- Manages PKCE flow, token exchange, and session lifecycle
91
+
92
+
**`Session` (src/session.ts)**
93
+
- Represents authenticated user session
94
+
- Properties: `did`, `handle`, `pdsUrl`, `accessToken`, `refreshToken`, `isExpired`
95
+
- `makeRequest()`: Makes DPoP-authenticated HTTP requests with automatic nonce handling
96
+
- Serializable via `toJSON()` / `fromJSON()` for storage
97
+
98
+
**Storage Implementations (src/storage.ts)**
99
+
- `MemoryStorage`: In-memory with TTL support (development/testing)
100
+
- `SQLiteStorage`: Example SQLite backend (reference implementation)
101
+
- `LocalStorage`: Browser localStorage wrapper
102
+
- All implement `OAuthStorage` interface: `get()`, `set()`, `delete()`
103
+
104
+
**Error Hierarchy (src/errors.ts)**
105
+
- Base: `OAuthError` (all OAuth errors inherit from this)
106
+
- Handle errors: `InvalidHandleError`, `HandleResolutionError`
107
+
- Discovery errors: `PDSDiscoveryError`, `AuthServerDiscoveryError`
108
+
- Flow errors: `TokenExchangeError`, `AuthorizationError`, `InvalidStateError`
109
+
- Auth errors: `DPoPError`, `SessionError`
110
+
111
+
### Critical Implementation Details
112
+
113
+
**Web Crypto API vs Node.js crypto**
114
+
- MUST use `crypto.subtle.generateKey()` with explicit `namedCurve: "P-256"`
115
+
- MUST use `jsr:@panva/jose` NOT `npm:jose` or Node.js jose packages
116
+
- DPoP key generation MUST set `extractable: true` for JWK export
117
+
- Private key import MUST clean JWK (remove conflicting `key_ops` from exportJWK)
118
+
119
+
**DPoP Nonce Handling**
120
+
- AT Protocol uses 400 status (not 401) for initial nonce challenges during token exchange
121
+
- Token refresh uses 401 status for nonce challenges
122
+
- Always check `DPoP-Nonce` header and retry with nonce if present
123
+
- Nonce included in JWT payload, not header
124
+
125
+
**Handle Resolution Strategy**
126
+
- Default: Slingshot with multi-level fallbacks
127
+
- `resolveMiniDoc` returns both DID + PDS in one request (preferred)
128
+
- Standard resolution requires two requests: handle→DID, then DID document→PDS
129
+
- PDS URL extracted from DID document's `AtprotoPersonalDataServer` service
130
+
131
+
**Session Storage Pattern**
132
+
- PKCE data stored with `pkce:{state}` prefix, 10-minute TTL
133
+
- Sessions stored with `session:{sessionId}` prefix
134
+
- Auto-refresh on restore if token expires within 5 minutes
135
+
- `isExpired` uses 5-minute buffer to prevent edge cases
136
+
137
+
## Testing Patterns
138
+
139
+
Tests use Deno's built-in test framework with the following patterns:
140
+
141
+
**Mock/Fake Pattern** (per user's global CLAUDE.md)
142
+
- Tests must NOT rely on external services
143
+
- Use injection patterns for all dependencies
144
+
- Mock storage, resolvers, and network calls in tests
145
+
- Test files: `tests/*_test.ts`
146
+
147
+
**Test File Structure**
148
+
- `errors_test.ts`: Error class behavior and messages
149
+
- `session_test.ts`: Session management, token refresh, serialization
150
+
- `storage_test.ts`: Storage implementations with TTL
151
+
- `utils_test.ts`: Utility functions (PKCE, DPoP, etc.)
152
+
153
+
## Security & OAuth Best Practices
154
+
155
+
**CRITICAL: No OAuth Workarounds** (per user's global CLAUDE.md)
156
+
- Always follow OAuth 2.0, AT Protocol, and DPoP specs exactly
157
+
- No shortcuts or "good enough" solutions for auth flows
158
+
- Properly validate state parameters (CSRF protection)
159
+
- Use secure PKCE (S256, not plain)
160
+
- DPoP proof must include all required claims
161
+
162
+
**Token Management**
163
+
- Store refresh tokens securely in storage backend
164
+
- Never log tokens or sensitive cryptographic material
165
+
- Clean up PKCE data after use (success or failure)
166
+
- Revoke tokens on sign out (best effort)
167
+
168
+
## Common Development Patterns
169
+
170
+
**Adding a new storage backend:**
171
+
1. Implement `OAuthStorage` interface from `src/types.ts`
172
+
2. Implement `get<T>()`, `set<T>()`, `delete()` with TTL support
173
+
3. Handle TTL expiration in `get()` (return null if expired)
174
+
4. Add tests following pattern in `tests/storage_test.ts`
175
+
176
+
**Adding a new resolver:**
177
+
1. Implement `HandleResolver` interface from `src/types.ts`
178
+
2. Implement `resolve(handle)` returning `{ did: string; pdsUrl: string }`
179
+
3. Throw `HandleResolutionError` on failure
180
+
4. Consider fallback mechanisms like `SlingshotResolver`
181
+
182
+
**Error handling:**
183
+
- Catch and re-throw with appropriate error class
184
+
- Preserve error cause chain for debugging
185
+
- All OAuth errors extend `OAuthError` base class
186
+
- Use specific error types for different failure modes
187
+
188
+
## Important Constraints
189
+
190
+
- **Handle-only inputs**: Client only accepts AT Protocol handles, not DIDs or URLs
191
+
- **Deno runtime**: Built for Deno, uses Web Standards APIs exclusively
192
+
- **No Node.js crypto**: Cannot use Node.js crypto modules (incompatible with Deno)
193
+
- **Slingshot dependency**: Default resolver uses third-party Slingshot service (can be configured)
194
+
- **ES256 only**: DPoP uses ECDSA P-256 (ES256), not RS256 or other algorithms
+6
README.md
+6
README.md
···
1
1
# @tijs/oauth-client-deno
2
2
3
+
[](https://ko-fi.com/tijsteulings)
4
+
3
5
A **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.
4
6
5
7
## 🎯 Opinionated Design
···
348
350
- **Cross-platform compatibility** that works in Deno, browsers, and other Web Standards environments
349
351
350
352
The implementation maintains full API compatibility with the original Node.js client while providing a native Web Standards foundation.
353
+
354
+
## ☕ Support Development
355
+
356
+
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.
351
357
352
358
## 🙏 Acknowledgments
353
359