···11+# ATProto OAuth React Demo
22+33+## Tech Stack
44+- **Framework**: React 18 with Vite
55+- **Language**: TypeScript 5.3 (strict mode)
66+- **Styling**: Tailwind CSS 3.4
77+- **State Management**: React Context API for auth
88+- **Routing**: React Router v6
99+- **HTTP Client**: Fetch API with custom hooks
1010+- **Authentication**: ATProtocol OAuth 2.1 with PKCE, DPoP, and PAR
1111+- **ATProto Libraries**:
1212+ - @atproto/oauth-client-browser for OAuth
1313+ - @atproto/api for XRPC and RichText
1414+ - @atproto/identity for handle/DID resolution
1515+1616+## Project Structure
1717+```
1818+src/
1919+├── components/
2020+│ ├── auth/ # Authentication components
2121+│ ├── post/ # Post creation components
2222+│ └── layout/ # Layout and navigation components
2323+├── contexts/ # React contexts (AuthContext)
2424+├── hooks/ # Custom hooks (useAuth, useATProto)
2525+├── lib/ # ATProtocol client setup
2626+├── utils/ # Utility functions (crypto, DPoP)
2727+└── types/ # TypeScript type definitions
2828+```
2929+3030+## Authentication Architecture
3131+3232+### OAuth 2.1 Flow
3333+1. **Discovery**: Handle → DID → PDS → Authorization Server
3434+2. **Session Secrets**: PKCE verifier, DPoP keypair, state token
3535+3. **PAR**: Pushed Authorization Request with nonce discovery
3636+4. **Authorization**: User consent at PDS
3737+5. **Callback**: Verify state and issuer
3838+6. **Token Exchange**: Code for tokens with DPoP proof
3939+7. **Identity Verification**: Verify sub (DID) matches expected
4040+4141+### Token Management
4242+- **Access Tokens**: 15-30 min lifetime, DPoP-bound
4343+- **Storage**: sessionStorage (never localStorage)
4444+- **DPoP Keys**: IndexedDB, non-exportable CryptoKeyPair
4545+- **Refresh Tokens**: Single-use with mandatory rotation
4646+- **Auto-refresh**: Before access token expiry
4747+4848+### Security Requirements
4949+- PKCE with S256 (mandatory)
5050+- DPoP with ES256 (mandatory)
5151+- PAR (Pushed Authorization Requests)
5252+- State parameter for CSRF protection
5353+- Issuer verification in callback
5454+- Bidirectional handle/DID verification
5555+5656+## Coding Standards
5757+5858+### TypeScript
5959+```typescript
6060+// ✅ Good
6161+interface AuthState {
6262+ user: ProfileView | null;
6363+ loading: boolean;
6464+ error: Error | null;
6565+}
6666+6767+// ❌ Bad
6868+interface AuthState {
6969+ user: any;
7070+ loading: any;
7171+ error: any;
7272+}
7373+```
7474+7575+### React Patterns
7676+- Functional components with hooks only
7777+- Custom hooks for reusable logic
7878+- Context for auth state management
7979+- Error boundaries for graceful failures
8080+- Suspense for code splitting
8181+8282+### Error Handling
8383+```typescript
8484+try {
8585+ const result = await oauthClient.authorize(handle);
8686+ // Handle success
8787+} catch (error) {
8888+ console.error('OAuth authorization failed:', error);
8989+ // User-friendly error message
9090+ setError('Failed to authenticate. Please try again.');
9191+}
9292+```
9393+9494+### Security Best Practices
9595+- **Never** store tokens in localStorage
9696+- **Always** verify DID matches expected identity
9797+- **Always** generate fresh DPoP proof per request
9898+- **Always** validate state parameter in callback
9999+- **Never** export DPoP private keys
100100+101101+## API Patterns
102102+103103+### XRPC Requests
104104+```typescript
105105+// Every request needs Authorization and DPoP headers
106106+const response = await fetch(`${pdsUrl}/xrpc/${nsid}`, {
107107+ method: 'POST',
108108+ headers: {
109109+ 'Authorization': `DPoP ${accessToken}`,
110110+ 'DPoP': dpopProof,
111111+ 'Content-Type': 'application/json'
112112+ },
113113+ body: JSON.stringify(data)
114114+});
115115+```
116116+117117+### Record Creation
118118+```typescript
119119+const record = {
120120+ repo: userDid,
121121+ collection: 'app.bsky.feed.post',
122122+ record: {
123123+ $type: 'app.bsky.feed.post',
124124+ text: postText,
125125+ createdAt: new Date().toISOString()
126126+ }
127127+};
128128+```
129129+130130+## Testing Guidelines
131131+132132+### Unit Tests
133133+- Crypto utilities (PKCE, DPoP generation)
134134+- DID resolution logic
135135+- Token refresh logic
136136+- Input validation
137137+138138+### Integration Tests
139139+- Complete OAuth flow (mocked)
140140+- XRPC requests with DPoP
141141+- Protected route navigation
142142+143143+## Environment Variables
144144+```env
145145+VITE_CLIENT_ID=https://oauth-spa.smokesignal.tools/client-metadata.json
146146+VITE_REDIRECT_URI=https://oauth-spa.smokesignal.tools/oauth/callback
147147+VITE_DEFAULT_PDS=https://bsky.social
148148+```
149149+150150+## Do's and Don'ts
151151+152152+### Do's
153153+✅ Use sessionStorage for tokens
154154+✅ Generate fresh DPoP proof for each request
155155+✅ Verify DID matches expected identity
156156+✅ Handle token refresh proactively
157157+✅ Clear all storage on logout
158158+✅ Use RichText library for text processing
159159+✅ Implement proper error boundaries
160160+✅ Follow TypeScript strict mode
161161+162162+### Don'ts
163163+❌ Use localStorage for sensitive data
164164+❌ Skip DPoP implementation
165165+❌ Forget bidirectional handle/DID verification
166166+❌ Use the same DPoP proof twice
167167+❌ Export DPoP private keys
168168+❌ Trust user input without validation
169169+❌ Use console.log in production
170170+❌ Create commits unless asked
171171+172172+## Development Commands
173173+```bash
174174+npm run dev # Start dev server
175175+npm run build # Build for production
176176+npm run preview # Preview production build
177177+npm run test # Run tests
178178+```
179179+180180+## Key Files
181181+- `public/client-metadata.json` - OAuth client metadata
182182+- `src/lib/atproto-client.ts` - ATProto client setup
183183+- `src/utils/crypto-utils.ts` - PKCE and DPoP utilities
184184+- `src/contexts/AuthContext.tsx` - Authentication state
185185+- `src/hooks/useAuth.ts` - Auth hook for components
+177
README.md
···11+# ATProtocol OAuth React Demo
22+33+A complete implementation of ATProtocol OAuth 2.1 authentication in a React Single Page Application. This demo showcases secure authentication with Bluesky and other ATProtocol-compatible Personal Data Servers using modern security features including PKCE, DPoP, and PAR.
44+55+## Features
66+77+- **OAuth 2.1 Authorization Code Flow** with mandatory PKCE (S256)
88+- **DPoP (Demonstrating Proof of Possession)** with ES256 token binding
99+- **PAR (Pushed Authorization Requests)** for enhanced security
1010+- **Automatic token refresh** before expiry
1111+- **XRPC API integration** for creating posts
1212+- **Secure token storage** (sessionStorage + IndexedDB for DPoP keys)
1313+- **Identity verification** with bidirectional handle/DID validation
1414+1515+## Prerequisites
1616+1717+- Node.js 18+ and npm
1818+- A Bluesky account (or account on any ATProtocol PDS)
1919+2020+## Installation
2121+2222+```bash
2323+# Install dependencies
2424+npm install
2525+```
2626+2727+## Configuration
2828+2929+The app is pre-configured for local development. The OAuth client metadata is served from `/public/client-metadata.json`:
3030+3131+```json
3232+{
3333+ "client_id": "https://oauth-spa.smokesignal.tools/client-metadata.json",
3434+ "application_type": "web",
3535+ "client_name": "ATProto Demo App",
3636+ "redirect_uris": ["https://oauth-spa.smokesignal.tools/oauth/callback"],
3737+ "scope": "atproto repo:garden.lexicon.oauth-masterclass.now",
3838+ "grant_types": ["authorization_code", "refresh_token"],
3939+ "response_types": ["code"],
4040+ "token_endpoint_auth_method": "none",
4141+ "dpop_bound_access_tokens": true
4242+}
4343+```
4444+4545+Environment variables (`.env`):
4646+4747+```env
4848+VITE_CLIENT_ID=https://oauth-spa.smokesignal.tools/client-metadata.json
4949+VITE_REDIRECT_URI=https://oauth-spa.smokesignal.tools/oauth/callback
5050+VITE_DEFAULT_PDS=https://bsky.social
5151+```
5252+5353+## Running the Application
5454+5555+```bash
5656+# Start the development server
5757+npm run dev
5858+5959+# The app will be available at https://oauth-spa.smokesignal.tools
6060+```
6161+6262+## Usage
6363+6464+1. **Home Page**: Visit `https://oauth-spa.smokesignal.tools` to see the landing page
6565+2. **Login**: Click "Get Started" and enter your Bluesky handle (e.g., `alice.bsky.social`)
6666+3. **Authorization**: You'll be redirected to your PDS to authorize the app
6767+4. **Dashboard**: After authorization, view your profile information
6868+5. **Create Post**: Navigate to "Create Post" to publish content to your PDS
6969+7070+## Authentication Flow
7171+7272+1. **Handle Resolution**: Converts handle → DID → PDS URL
7373+2. **PAR Request**: Registers authorization request with server
7474+3. **User Authorization**: User approves scopes at their PDS
7575+4. **Token Exchange**: Authorization code → Access + Refresh tokens
7676+5. **Identity Verification**: Validates DID matches expected identity
7777+6. **API Access**: Make authenticated XRPC calls with DPoP proofs
7878+7979+## Project Structure
8080+8181+```
8282+src/
8383+├── components/
8484+│ ├── auth/ # Authentication components
8585+│ │ ├── LoginForm.tsx
8686+│ │ └── OAuthCallback.tsx
8787+│ ├── post/ # Post creation
8888+│ │ └── CreatePost.tsx
8989+│ └── layout/ # Layout components
9090+│ ├── Header.tsx
9191+│ └── ProtectedRoute.tsx
9292+├── contexts/
9393+│ └── AuthContext.tsx # Auth state management
9494+├── lib/
9595+│ ├── atproto-oauth-client.ts # OAuth client
9696+│ └── xrpc-client.ts # XRPC API client
9797+├── utils/
9898+│ ├── crypto-utils.ts # PKCE & DPoP generation
9999+│ └── did-resolver.ts # Handle/DID resolution
100100+├── types/
101101+│ └── auth.ts # TypeScript definitions
102102+└── App.tsx # Main app with routing
103103+```
104104+105105+## Security Features
106106+107107+- **PKCE S256**: Protects against authorization code interception
108108+- **DPoP Token Binding**: Prevents token theft and replay attacks
109109+- **PAR**: Pre-registers authorization requests for validation
110110+- **Secure Storage**: Tokens in sessionStorage, DPoP keys in IndexedDB
111111+- **Identity Verification**: Bidirectional handle ↔ DID validation
112112+- **HTTPS Client Metadata**: Client configuration served over HTTPS
113113+114114+## Building for Production
115115+116116+```bash
117117+# Build the application
118118+npm run build
119119+120120+# Preview the production build
121121+npm run preview
122122+```
123123+124124+For production deployment:
125125+126126+1. Update `client-metadata.json` with your production URLs
127127+2. Host the metadata file at a publicly accessible HTTPS URL
128128+3. Update environment variables with production values
129129+4. Deploy to a secure HTTPS host
130130+131131+## Technologies Used
132132+133133+- **React 18** with TypeScript
134134+- **Vite** for build tooling
135135+- **React Router v6** for navigation
136136+- **Tailwind CSS** for styling
137137+- **Web Crypto API** for cryptographic operations
138138+- **IndexedDB** for secure key storage
139139+- **@atproto/api** for ATProtocol types and utilities
140140+141141+## Important Security Notes
142142+143143+- Never store tokens in localStorage (XSS vulnerability)
144144+- Always verify the DID matches the expected identity
145145+- Generate fresh DPoP proofs for every request
146146+- Implement proper CSRF protection with state parameter
147147+- Use HTTPS in production for all endpoints
148148+- Keep DPoP private keys non-exportable
149149+150150+## Troubleshooting
151151+152152+### "No OAuth state found"
153153+Clear browser storage and try logging in again.
154154+155155+### "DPoP key not found"
156156+IndexedDB may be blocked. Check browser settings.
157157+158158+### "Failed to resolve handle"
159159+Ensure the handle is valid and the PDS is accessible.
160160+161161+### "Token refresh failed"
162162+The session may have expired. Log in again.
163163+164164+## License
165165+166166+MIT
167167+168168+## Contributing
169169+170170+Contributions are welcome! Please ensure all OAuth security requirements are maintained.
171171+172172+## Resources
173173+174174+- [ATProtocol OAuth Specification](https://atproto.com/specs/oauth)
175175+- [Bluesky Documentation](https://docs.bsky.app)
176176+- [OAuth 2.1 RFC](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/)
177177+- [DPoP RFC 9449](https://datatracker.ietf.org/doc/rfc9449/)