ATProtocol OAuth React Demo#
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.
Features#
- OAuth 2.1 Authorization Code Flow with mandatory PKCE (S256)
- DPoP (Demonstrating Proof of Possession) with ES256 token binding
- PAR (Pushed Authorization Requests) for enhanced security
- Automatic token refresh before expiry
- XRPC API integration for creating posts
- Secure token storage (sessionStorage + IndexedDB for DPoP keys)
- Identity verification with bidirectional handle/DID validation
Prerequisites#
- Node.js 18+ and npm
- A Bluesky account (or account on any ATProtocol PDS)
Installation#
# Install dependencies
npm install
Configuration#
The app is pre-configured for local development. The OAuth client metadata is served from /public/client-metadata.json:
{
"client_id": "https://oauth-spa.smokesignal.tools/client-metadata.json",
"application_type": "web",
"client_name": "ATProto Demo App",
"redirect_uris": ["https://oauth-spa.smokesignal.tools/oauth/callback"],
"scope": "atproto repo:garden.lexicon.oauth-masterclass.now",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"dpop_bound_access_tokens": true
}
Environment variables (.env):
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
Running the Application#
# Start the development server
npm run dev
# The app will be available at https://oauth-spa.smokesignal.tools
Usage#
- Home Page: Visit
https://oauth-spa.smokesignal.toolsto see the landing page - Login: Click "Get Started" and enter your Bluesky handle (e.g.,
alice.bsky.social) - Authorization: You'll be redirected to your PDS to authorize the app
- Dashboard: After authorization, view your profile information
- Create Post: Navigate to "Create Post" to publish content to your PDS
Authentication Flow#
- Handle Resolution: Converts handle → DID → PDS URL
- PAR Request: Registers authorization request with server
- User Authorization: User approves scopes at their PDS
- Token Exchange: Authorization code → Access + Refresh tokens
- Identity Verification: Validates DID matches expected identity
- API Access: Make authenticated XRPC calls with DPoP proofs
Project Structure#
src/
├── components/
│ ├── auth/ # Authentication components
│ │ ├── LoginForm.tsx
│ │ └── OAuthCallback.tsx
│ ├── post/ # Post creation
│ │ └── CreatePost.tsx
│ └── layout/ # Layout components
│ ├── Header.tsx
│ └── ProtectedRoute.tsx
├── contexts/
│ └── AuthContext.tsx # Auth state management
├── lib/
│ ├── atproto-oauth-client.ts # OAuth client
│ └── xrpc-client.ts # XRPC API client
├── utils/
│ ├── crypto-utils.ts # PKCE & DPoP generation
│ └── did-resolver.ts # Handle/DID resolution
├── types/
│ └── auth.ts # TypeScript definitions
└── App.tsx # Main app with routing
Security Features#
- PKCE S256: Protects against authorization code interception
- DPoP Token Binding: Prevents token theft and replay attacks
- PAR: Pre-registers authorization requests for validation
- Secure Storage: Tokens in sessionStorage, DPoP keys in IndexedDB
- Identity Verification: Bidirectional handle ↔ DID validation
- HTTPS Client Metadata: Client configuration served over HTTPS
Building for Production#
# Build the application
npm run build
# Preview the production build
npm run preview
For production deployment:
- Update
client-metadata.jsonwith your production URLs - Host the metadata file at a publicly accessible HTTPS URL
- Update environment variables with production values
- Deploy to a secure HTTPS host
Technologies Used#
- React 18 with TypeScript
- Vite for build tooling
- React Router v6 for navigation
- Tailwind CSS for styling
- Web Crypto API for cryptographic operations
- IndexedDB for secure key storage
- @atproto/api for ATProtocol types and utilities
Important Security Notes#
- Never store tokens in localStorage (XSS vulnerability)
- Always verify the DID matches the expected identity
- Generate fresh DPoP proofs for every request
- Implement proper CSRF protection with state parameter
- Use HTTPS in production for all endpoints
- Keep DPoP private keys non-exportable
Troubleshooting#
"No OAuth state found"#
Clear browser storage and try logging in again.
"DPoP key not found"#
IndexedDB may be blocked. Check browser settings.
"Failed to resolve handle"#
Ensure the handle is valid and the PDS is accessible.
"Token refresh failed"#
The session may have expired. Log in again.
License#
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing#
Contributions are welcome! Please ensure all OAuth security requirements are maintained.