this repo has no description
1# ATProto OAuth React Demo
2
3## Tech Stack
4- **Framework**: React 18 with Vite
5- **Language**: TypeScript 5.3 (strict mode)
6- **Styling**: Tailwind CSS 3.4
7- **State Management**: React Context API for auth
8- **Routing**: React Router v6
9- **HTTP Client**: Fetch API with custom hooks
10- **Authentication**: ATProtocol OAuth 2.1 with PKCE, DPoP, and PAR
11- **ATProto Libraries**:
12 - @atproto/oauth-client-browser for OAuth
13 - @atproto/api for XRPC and RichText
14 - @atproto/identity for handle/DID resolution
15
16## Project Structure
17```
18src/
19├── components/
20│ ├── auth/ # Authentication components
21│ ├── post/ # Post creation components
22│ └── layout/ # Layout and navigation components
23├── contexts/ # React contexts (AuthContext)
24├── hooks/ # Custom hooks (useAuth, useATProto)
25├── lib/ # ATProtocol client setup
26├── utils/ # Utility functions (crypto, DPoP)
27└── types/ # TypeScript type definitions
28```
29
30## Authentication Architecture
31
32### OAuth 2.1 Flow
331. **Discovery**: Handle → DID → PDS → Authorization Server
342. **Session Secrets**: PKCE verifier, DPoP keypair, state token
353. **PAR**: Pushed Authorization Request with nonce discovery
364. **Authorization**: User consent at PDS
375. **Callback**: Verify state and issuer
386. **Token Exchange**: Code for tokens with DPoP proof
397. **Identity Verification**: Verify sub (DID) matches expected
40
41### Token Management
42- **Access Tokens**: 15-30 min lifetime, DPoP-bound
43- **Storage**: sessionStorage (never localStorage)
44- **DPoP Keys**: IndexedDB, non-exportable CryptoKeyPair
45- **Refresh Tokens**: Single-use with mandatory rotation
46- **Auto-refresh**: Before access token expiry
47
48### Security Requirements
49- PKCE with S256 (mandatory)
50- DPoP with ES256 (mandatory)
51- PAR (Pushed Authorization Requests)
52- State parameter for CSRF protection
53- Issuer verification in callback
54- Bidirectional handle/DID verification
55
56## Coding Standards
57
58### TypeScript
59```typescript
60// ✅ Good
61interface AuthState {
62 user: ProfileView | null;
63 loading: boolean;
64 error: Error | null;
65}
66
67// ❌ Bad
68interface AuthState {
69 user: any;
70 loading: any;
71 error: any;
72}
73```
74
75### React Patterns
76- Functional components with hooks only
77- Custom hooks for reusable logic
78- Context for auth state management
79- Error boundaries for graceful failures
80- Suspense for code splitting
81
82### Error Handling
83```typescript
84try {
85 const result = await oauthClient.authorize(handle);
86 // Handle success
87} catch (error) {
88 console.error('OAuth authorization failed:', error);
89 // User-friendly error message
90 setError('Failed to authenticate. Please try again.');
91}
92```
93
94### Security Best Practices
95- **Never** store tokens in localStorage
96- **Always** verify DID matches expected identity
97- **Always** generate fresh DPoP proof per request
98- **Always** validate state parameter in callback
99- **Never** export DPoP private keys
100
101## API Patterns
102
103### XRPC Requests
104```typescript
105// Every request needs Authorization and DPoP headers
106const response = await fetch(`${pdsUrl}/xrpc/${nsid}`, {
107 method: 'POST',
108 headers: {
109 'Authorization': `DPoP ${accessToken}`,
110 'DPoP': dpopProof,
111 'Content-Type': 'application/json'
112 },
113 body: JSON.stringify(data)
114});
115```
116
117### Record Creation
118```typescript
119const record = {
120 repo: userDid,
121 collection: 'app.bsky.feed.post',
122 record: {
123 $type: 'app.bsky.feed.post',
124 text: postText,
125 createdAt: new Date().toISOString()
126 }
127};
128```
129
130## Testing Guidelines
131
132### Unit Tests
133- Crypto utilities (PKCE, DPoP generation)
134- DID resolution logic
135- Token refresh logic
136- Input validation
137
138### Integration Tests
139- Complete OAuth flow (mocked)
140- XRPC requests with DPoP
141- Protected route navigation
142
143## Environment Variables
144```env
145VITE_CLIENT_ID=https://oauth-spa.smokesignal.tools/client-metadata.json
146VITE_REDIRECT_URI=https://oauth-spa.smokesignal.tools/oauth/callback
147VITE_DEFAULT_PDS=https://bsky.social
148```
149
150## Do's and Don'ts
151
152### Do's
153✅ Use sessionStorage for tokens
154✅ Generate fresh DPoP proof for each request
155✅ Verify DID matches expected identity
156✅ Handle token refresh proactively
157✅ Clear all storage on logout
158✅ Use RichText library for text processing
159✅ Implement proper error boundaries
160✅ Follow TypeScript strict mode
161
162### Don'ts
163❌ Use localStorage for sensitive data
164❌ Skip DPoP implementation
165❌ Forget bidirectional handle/DID verification
166❌ Use the same DPoP proof twice
167❌ Export DPoP private keys
168❌ Trust user input without validation
169❌ Use console.log in production
170❌ Create commits unless asked
171
172## Development Commands
173```bash
174npm run dev # Start dev server
175npm run build # Build for production
176npm run preview # Preview production build
177npm run test # Run tests
178```
179
180## Key Files
181- `public/client-metadata.json` - OAuth client metadata
182- `src/lib/atproto-client.ts` - ATProto client setup
183- `src/utils/crypto-utils.ts` - PKCE and DPoP utilities
184- `src/contexts/AuthContext.tsx` - Authentication state
185- `src/hooks/useAuth.ts` - Auth hook for components