TypeScript 98.8%
JavaScript 0.8%
HTML 0.3%
CSS 0.1%
2 1 0

Clone this repository

https://tangled.org/ngerakines.me/oauth-masterclass-react
git@tangled.org:ngerakines.me/oauth-masterclass-react

For self-hosted knots, clone URLs may differ based on your setup.

README.md

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#

  1. Home Page: Visit https://oauth-spa.smokesignal.tools to see the landing page
  2. Login: Click "Get Started" and enter your Bluesky handle (e.g., alice.bsky.social)
  3. Authorization: You'll be redirected to your PDS to authorize the app
  4. Dashboard: After authorization, view your profile information
  5. Create Post: Navigate to "Create Post" to publish content to your PDS

Authentication Flow#

  1. Handle Resolution: Converts handle → DID → PDS URL
  2. PAR Request: Registers authorization request with server
  3. User Authorization: User approves scopes at their PDS
  4. Token Exchange: Authorization code → Access + Refresh tokens
  5. Identity Verification: Validates DID matches expected identity
  6. 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:

  1. Update client-metadata.json with your production URLs
  2. Host the metadata file at a publicly accessible HTTPS URL
  3. Update environment variables with production values
  4. 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.

Resources#