this repo has no description
ATProto OAuth React Demo#
Tech Stack#
- Framework: React 18 with Vite
- Language: TypeScript 5.3 (strict mode)
- Styling: Tailwind CSS 3.4
- State Management: React Context API for auth
- Routing: React Router v6
- HTTP Client: Fetch API with custom hooks
- Authentication: ATProtocol OAuth 2.1 with PKCE, DPoP, and PAR
- ATProto Libraries:
- @atproto/oauth-client-browser for OAuth
- @atproto/api for XRPC and RichText
- @atproto/identity for handle/DID resolution
Project Structure#
src/
├── components/
│ ├── auth/ # Authentication components
│ ├── post/ # Post creation components
│ └── layout/ # Layout and navigation components
├── contexts/ # React contexts (AuthContext)
├── hooks/ # Custom hooks (useAuth, useATProto)
├── lib/ # ATProtocol client setup
├── utils/ # Utility functions (crypto, DPoP)
└── types/ # TypeScript type definitions
Authentication Architecture#
OAuth 2.1 Flow#
- Discovery: Handle → DID → PDS → Authorization Server
- Session Secrets: PKCE verifier, DPoP keypair, state token
- PAR: Pushed Authorization Request with nonce discovery
- Authorization: User consent at PDS
- Callback: Verify state and issuer
- Token Exchange: Code for tokens with DPoP proof
- Identity Verification: Verify sub (DID) matches expected
Token Management#
- Access Tokens: 15-30 min lifetime, DPoP-bound
- Storage: sessionStorage (never localStorage)
- DPoP Keys: IndexedDB, non-exportable CryptoKeyPair
- Refresh Tokens: Single-use with mandatory rotation
- Auto-refresh: Before access token expiry
Security Requirements#
- PKCE with S256 (mandatory)
- DPoP with ES256 (mandatory)
- PAR (Pushed Authorization Requests)
- State parameter for CSRF protection
- Issuer verification in callback
- Bidirectional handle/DID verification
Coding Standards#
TypeScript#
// ✅ Good
interface AuthState {
user: ProfileView | null;
loading: boolean;
error: Error | null;
}
// ❌ Bad
interface AuthState {
user: any;
loading: any;
error: any;
}
React Patterns#
- Functional components with hooks only
- Custom hooks for reusable logic
- Context for auth state management
- Error boundaries for graceful failures
- Suspense for code splitting
Error Handling#
try {
const result = await oauthClient.authorize(handle);
// Handle success
} catch (error) {
console.error('OAuth authorization failed:', error);
// User-friendly error message
setError('Failed to authenticate. Please try again.');
}
Security Best Practices#
- Never store tokens in localStorage
- Always verify DID matches expected identity
- Always generate fresh DPoP proof per request
- Always validate state parameter in callback
- Never export DPoP private keys
API Patterns#
XRPC Requests#
// Every request needs Authorization and DPoP headers
const response = await fetch(`${pdsUrl}/xrpc/${nsid}`, {
method: 'POST',
headers: {
'Authorization': `DPoP ${accessToken}`,
'DPoP': dpopProof,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
Record Creation#
const record = {
repo: userDid,
collection: 'app.bsky.feed.post',
record: {
$type: 'app.bsky.feed.post',
text: postText,
createdAt: new Date().toISOString()
}
};
Testing Guidelines#
Unit Tests#
- Crypto utilities (PKCE, DPoP generation)
- DID resolution logic
- Token refresh logic
- Input validation
Integration Tests#
- Complete OAuth flow (mocked)
- XRPC requests with DPoP
- Protected route navigation
Environment Variables#
VITE_CLIENT_ID=https://oauth-spa.smokesignal.tools/client-metadata.json
VITE_REDIRECT_URI=https://oauth-spa.smokesignal.tools/oauth/callback
VITE_DEFAULT_PDS=https://bsky.social
Do's and Don'ts#
Do's#
✅ Use sessionStorage for tokens ✅ Generate fresh DPoP proof for each request ✅ Verify DID matches expected identity ✅ Handle token refresh proactively ✅ Clear all storage on logout ✅ Use RichText library for text processing ✅ Implement proper error boundaries ✅ Follow TypeScript strict mode
Don'ts#
❌ Use localStorage for sensitive data ❌ Skip DPoP implementation ❌ Forget bidirectional handle/DID verification ❌ Use the same DPoP proof twice ❌ Export DPoP private keys ❌ Trust user input without validation ❌ Use console.log in production ❌ Create commits unless asked
Development Commands#
npm run dev # Start dev server
npm run build # Build for production
npm run preview # Preview production build
npm run test # Run tests
Key Files#
public/client-metadata.json- OAuth client metadatasrc/lib/atproto-client.ts- ATProto client setupsrc/utils/crypto-utils.ts- PKCE and DPoP utilitiessrc/contexts/AuthContext.tsx- Authentication statesrc/hooks/useAuth.ts- Auth hook for components