OAuth Migration Guide: Custom Implementation → @atproto/oauth-client-browser#
This guide explains how to migrate from your current custom OAuth implementation to the official @atproto/oauth-client-browser package, which provides better reliability, maintenance, and features.
Overview of Changes#
What's Being Replaced#
Current Implementation:
- Custom PKCE flow with manual code generation (
bluesky-auth.ts) - Manual DPoP token generation and nonce handling
- Complex token refresh logic with multiple retry strategies
- Custom API proxy routes (
/api/auth/token,/api/auth/nonce) - Manual storage management across localStorage/sessionStorage
- Complex auth context with manual state management
New Implementation:
- Official
@atproto/oauth-client-browserwith automatic session management - Automatic token refresh and DPoP handling
- Built-in IndexedDB storage
- Direct integration with
@atproto/api - Simplified auth context
- No need for custom API routes
Migration Steps#
1. Update App Layout to Use New Auth Context#
Replace the old auth context with the new one in your main layout:
// Before: app/src/app/layout.tsx
import { AuthProvider } from '@/lib/auth-context'
// After:
import { AuthProvider } from '@/lib/auth-context-new'
2. Replace Login Page#
// Replace: app/src/app/auth/login/page.tsx
// With: app/src/app/auth/login/page-new.tsx
// Then rename page-new.tsx to page.tsx
3. Replace Callback Page#
// Replace: app/src/app/auth/callback/page.tsx
// With: app/src/app/auth/callback/page-new.tsx
// Then rename page-new.tsx to page.tsx
4. Update API Calls#
Replace your existing API calls with the new simplified client:
// Before:
import { getProfile, makeAuthenticatedRequest } from '@/lib/bluesky-api'
import { useAuth } from '@/lib/auth-context'
const { accessToken, keyPair, dpopNonce, pdsEndpoint } = useAuth()
const profile = await getProfile(accessToken, keyPair, dpopNonce, handle, pdsEndpoint)
// After:
import { getProfile } from '@/lib/api-client'
import { useAuth } from '@/lib/auth-context-new'
const { session } = useAuth()
if (session) {
const profile = await getProfile(session)
}
5. Update Components Using Auth#
Update any components that use the auth context:
// Before:
const { isAuthenticated, accessToken, did, handle } = useAuth()
// After:
const { isAuthenticated, session } = useAuth()
const did = session?.sub
const handle = session?.info?.handle
// Note: accessToken is available as session?.accessToken if needed for legacy code
6. Remove Old Files#
After migration is complete and tested, you can remove these files:
app/src/lib/bluesky-auth.ts- Custom OAuth implementationapp/src/lib/auth-context.tsx- Old auth context (rename from auth-context-new.tsx)app/src/lib/storage-util.ts- Custom storage utilitiesapp/src/app/api/auth/token/route.ts- Custom token exchange APIapp/src/app/api/auth/nonce/route.ts- Custom nonce retrieval API- Old login and callback pages after replacement
7. Update Existing API Usage#
Replace complex API calls with simplified versions:
// Before: Making a post
import { createPost } from '@/lib/bluesky-api'
await createPost(accessToken, keyPair, dpopNonce, postData, pdsEndpoint)
// After:
import { createPost } from '@/lib/api-client'
await createPost(session, { text: "Hello world!" })
Benefits of Migration#
1. Simplified Codebase#
- ~1000 lines of custom OAuth code removed
- No more manual DPoP token generation
- No more complex nonce handling
- No more custom API routes
2. Better Reliability#
- Official implementation tested across many apps
- Automatic retry logic for failed requests
- Better error handling and recovery
- Proper session lifecycle management
3. Improved Security#
- Uses secure IndexedDB storage instead of localStorage
- Proper token refresh with automatic retries
- Better handling of session invalidation
- DPoP implementation follows latest specs
4. Enhanced Features#
- Automatic handle resolution
- Built-in support for third-party PDS servers
- Session restoration across browser sessions
- Event listeners for session changes
5. Better Maintenance#
- Official package maintained by AT Protocol team
- Regular updates and security patches
- Better TypeScript support
- Comprehensive documentation
Compatibility Notes#
Third-Party PDS Support#
The new implementation maintains full support for third-party PDS servers like geese.blue. The OAuth client automatically:
- Resolves handle to find the correct PDS
- Uses the appropriate OAuth endpoints
- Manages cross-PDS authentication flows
Legacy Code Support#
The new auth context provides backward compatibility properties:
accessToken- Available assession?.accessTokenrefreshToken- Available assession?.refreshTokendid- Available assession?.subhandle- Available assession?.info?.handlepdsEndpoint- Extracted from session info
Testing the Migration#
1. Test Basic Authentication#
- Sign in with a Bluesky handle (e.g.,
alice.bsky.social) - Verify the callback completes successfully
- Check that session is restored on page refresh
2. Test Third-Party PDS#
- Sign in with a third-party PDS handle (e.g.,
alice.geese.blue) - Verify it resolves to the correct PDS
- Test that API calls work correctly
3. Test Session Management#
- Sign in and close the browser
- Reopen and verify session is restored
- Test sign out functionality
4. Test API Calls#
- Verify profile loading works
- Test creating posts
- Test liking/unliking posts
- Test following/unfollowing users
Rollback Plan#
If issues arise, you can quickly rollback:
- Revert the auth context import in
layout.tsx - Restore the original login/callback pages
- Keep the old implementation files until migration is stable
The old API routes and implementation can remain in place during testing for safety.
Support#
If you encounter issues during migration:
- Check browser console for OAuth client errors
- Verify the client metadata URL is accessible
- Test with different handle types (Bluesky vs third-party)
- Check that the redirect URI matches exactly
The new implementation should handle most edge cases that the custom implementation addressed, but with much less complexity.