# OAuth Authentication Setup Guide This project uses the [atcute](https://github.com/mary-ext/atcute) library for AT Protocol OAuth authentication, providing a lightweight and modern approach to user authentication with Bluesky and other AT Protocol services. ## Overview The authentication system is built with: - **@atcute/oauth-node-client** - OAuth client for Node.js environments - **@atcute/identity-resolver** - Handle and DID resolution - **@atcute/client** - AT Protocol API client - **JWT-based session management** - Secure token signing with ES256 ## Setup Instructions ### 1. Generate OAuth Private Key The OAuth system requires a private key for JWT signing. Generate one by running: ```bash npm run setup:key ``` This will: - Generate an ES256 private key - Save it to `.env` as `PRIVATE_KEY_JWK` - Update `.env.example` with a placeholder **IMPORTANT**: Keep this key secure and never commit it to version control! ### 2. Environment Configuration Ensure your `.env` file contains: ```bash # Database DATABASE_PATH=./data/app.db # OAuth Configuration PUBLIC_BASE_URL=http://localhost:5173 PRIVATE_KEY_JWK={"kty":"EC","x":"...","y":"...","crv":"P-256","d":"...","alg":"ES256"} # Session (not currently used but reserved for future encryption) SESSION_SECRET=change-this-to-a-random-secret-in-production ``` For production: - Use **HTTPS** and update `PUBLIC_BASE_URL` accordingly - Generate a strong `SESSION_SECRET` for additional security ### 3. Start the Development Server ```bash npm run dev ``` The server will start on `http://localhost:5173` ## Authentication Flow ### 1. Login Endpoint (`POST /auth/login`) Users can log in with their Bluesky handle, DID, or PDS service URL: ```javascript // Example request POST /auth/login Content-Type: application/json { "handle": "alice.bsky.social" // or "did:plc:..." or "https://bsky.social" } ``` **Process:** 1. Validates the identifier format 2. Determines the target type (account, DID, or PDS) 3. Initiates OAuth authorization with the AT Protocol service 4. Redirects user to the authorization page ### 2. OAuth Callback (`GET /auth/callback`) After user authorizes on their AT Protocol service: 1. Receives authorization code via query parameters 2. Exchanges code for access tokens 3. Stores the user's DID in a secure HTTP-only cookie 4. Redirects to home page ### 3. Session Management Sessions are managed through: - **Cookie Storage**: User's DID stored in `go_session` cookie - **OAuth Session Store**: In-memory store (MemoryStore) for session data - Supports up to 10,000 sessions - Includes automatic token refresh - **State Store**: Temporary storage for OAuth state (10-minute TTL) **Session Restoration:** ```typescript // The getSession() function automatically restores and refreshes OAuth sessions const session = await getSession(event); if (session) { // User is authenticated console.log('User DID:', session.did); } ``` ### 4. Logout Endpoint (`POST /auth/logout`) Properly signs out the user: 1. Revokes OAuth tokens at the authorization server 2. Clears local session cookie 3. Redirects to home page ## OAuth Discovery Endpoints The following endpoints are required for OAuth discovery: ### `/oauth-client-metadata.json` Serves the OAuth client metadata including: - `client_id` - Identifies this application - `redirect_uris` - Where users return after authorization - `scope` - Requested permissions (`atproto`) - `jwks_uri` - Public key endpoint ### `/jwks.json` Serves the JSON Web Key Set (JWKS) containing the public keys for token verification. ## Identity Resolution The system uses comprehensive identity resolution: ### Handle Resolution - **DNS Resolution**: Resolves handles via DNS TXT records - **HTTP Resolution**: Falls back to `.well-known` endpoint ### DID Document Resolution - **PLC**: Resolves `did:plc:*` via PLC directory - **Web**: Resolves `did:web:*` via HTTPS ## Security Considerations ### Current Implementation - ✅ HTTP-only cookies prevent XSS attacks - ✅ SameSite=Lax prevents CSRF attacks - ✅ Automatic token refresh - ✅ Proper token revocation on logout - ✅ JWT signing with ES256 ### Production Recommendations 1. **Use HTTPS**: Enable `secure: true` for cookies 2. **Persistent Storage**: Replace `MemoryStore` with Redis or database 3. **Session Encryption**: Implement additional cookie encryption with `SESSION_SECRET` 4. **Rate Limiting**: Add rate limits to auth endpoints 5. **Monitoring**: Log authentication events for security auditing ## Adapting for Production ### Replace In-Memory Stores For production deployments, replace the in-memory stores with persistent storage: ```typescript // Example with Redis (pseudo-code) import { RedisStore } from './redis-store'; // implement this const oauth = new OAuthClient({ // ...other config stores: { sessions: new RedisStore({ client: redisClient, prefix: 'oauth:session:', }), states: new RedisStore({ client: redisClient, prefix: 'oauth:state:', ttl: TEN_MINUTES_MS, }), }, }); ``` ### Cookie Encryption Update `setSession()` in `src/lib/server/auth.ts` to encrypt cookies: ```typescript import { createCipheriv, createDecipheriv } from 'crypto'; // Implement encryption/decryption using SESSION_SECRET ``` ## API Reference ### Auth Functions (`src/lib/server/auth.ts`) #### `getOAuthClient(): Promise` Returns the configured OAuth client instance (singleton). #### `getSession(event: RequestEvent): Promise` Retrieves and validates the current user session. - Automatically refreshes expired tokens - Returns `null` if session is invalid #### `setSession(event: RequestEvent, session: Session): Promise` Stores a new session in a secure cookie. #### `clearSession(event: RequestEvent): Promise` Removes the session cookie. ### Session Interface ```typescript interface Session { did: string; // Decentralized Identifier (e.g., "did:plc:...") handle?: string; // Optional AT Protocol handle } ``` ## Troubleshooting ### "PRIVATE_KEY_JWK environment variable is required" Run `npm run setup:key` to generate the private key. ### "OAuth callback error" Check that: 1. `PUBLIC_BASE_URL` is correctly set in `.env` 2. The URL is accessible from the browser 3. For local development, use `http://localhost:5173` (not `127.0.0.1`) ### Session not persisting 1. Check browser console for cookie errors 2. Ensure cookies are enabled 3. Verify `PUBLIC_BASE_URL` matches your actual URL ### Token refresh failures 1. The MemoryStore loses data on server restart (expected in development) 2. For production, implement persistent storage ## Testing the Implementation ### Manual Testing 1. Navigate to `http://localhost:5173` 2. Click "Login" and enter your Bluesky handle (e.g., `alice.bsky.social`) 3. Authorize the application on Bluesky 4. You should be redirected back and see your session ### Test with curl ```bash # Login (get redirect URL) curl -X POST http://localhost:5173/auth/login \ -H "Content-Type: application/json" \ -d '{"handle":"alice.bsky.social"}' \ -i # Check metadata curl http://localhost:5173/oauth-client-metadata.json | jq # Check JWKS curl http://localhost:5173/jwks.json | jq ``` ## Resources - [atcute GitHub Repository](https://github.com/mary-ext/atcute) - [AT Protocol OAuth Guide](https://atproto.com/guides/oauth) - [AT Protocol Documentation](https://atproto.com) - [Bluesky API Docs](https://docs.bsky.app) ## Next Steps - [ ] Implement persistent session storage (Redis/PostgreSQL) - [ ] Add session encryption with SESSION_SECRET - [ ] Implement rate limiting - [ ] Add authentication middleware for protected routes - [ ] Fetch and display user handle/profile - [ ] Add refresh token rotation - [ ] Implement proper error pages - [ ] Add security headers (HSTS, CSP, etc.)