ATProtocol OAuth iOS Demo#
A complete iOS application demonstrating ATProtocol OAuth 2.1 authentication with PKCE, DPoP, and PAR. This app authenticates users with the Bluesky/ATProtocol network and demonstrates making authenticated XRPC requests.
Features#
- ✅ Full OAuth 2.1 implementation with PKCE (Proof Key for Code Exchange)
- ✅ DPoP (Demonstrating Proof of Possession) for token binding
- ✅ PAR (Pushed Authorization Requests)
- ✅ Secure token storage in iOS Keychain
- ✅ Handle and DID resolution
- ✅ ASWebAuthenticationSession for secure authentication
- ✅ XRPC API calls (create post demo)
- ✅ SwiftUI interface with MVVM pattern
Requirements#
- iOS 14.0+
- Xcode 14.0+
- Swift 5.9+
- A Bluesky account (create free at https://bsky.app)
Project Structure#
ATProtoOAuthDemo/
├── ATProtoOAuthDemoApp.swift # Main app entry point
├── Info.plist # App configuration with URL scheme
├── Models/
│ ├── OAuthModels.swift # OAuth data structures
│ ├── DIDDocument.swift # DID document models
│ └── XRPCModels.swift # XRPC request/response models
├── Authentication/
│ ├── AuthenticationManager.swift # Main auth coordinator
│ ├── OAuthClient.swift # OAuth flow implementation
│ ├── PKCEGenerator.swift # PKCE code generation
│ ├── DPoPGenerator.swift # DPoP JWT generation
│ ├── IdentityResolver.swift # Handle/DID resolution
│ └── KeychainManager.swift # Secure token storage
├── Networking/
│ └── XRPCClient.swift # XRPC API client
├── Views/
│ ├── ContentView.swift # Root view
│ ├── LoginView.swift # Login interface
│ ├── AuthenticatedView.swift # Post-login view
│ └── CreatePostView.swift # Create post demo
└── Utilities/
└── Constants.swift # App constants
Setup Instructions#
1. Create a New Xcode Project#
- Open Xcode
- Create a new iOS App project
- Product Name: "ATProtoOAuthDemo"
- Interface: SwiftUI
- Language: Swift
- Deployment Target: iOS 14.0 or later
2. Add Source Files#
Copy all the Swift files from the ATProtoOAuthDemo directory into your Xcode project, maintaining the folder structure.
3. Configure Info.plist#
The Info.plist file is already configured with the custom URL scheme me.ngerakines.atprotodemo. This is used for OAuth callbacks.
Important: If you change the bundle identifier, update the URL scheme in both:
Info.plist: UpdateCFBundleURLSchemesConstants.swift: UpdateurlSchemeconstant
4. Build and Run#
- Select a simulator or device
- Press Cmd+R to build and run
- The app will launch and show the login screen
Usage#
Authenticating#
- Launch the app
- Enter a Bluesky handle (e.g., "yourname.bsky.social")
- Tap "Sign In with OAuth"
- You'll be redirected to the Bluesky authorization page
- Log in and approve the authorization
- You'll be redirected back to the app
Creating a Post#
- After authentication, tap "Create Post"
- Enter your post text (max 300 characters)
- Tap "Post"
- The post will be created via XRPC and appear on your Bluesky feed
Technical Details#
OAuth Flow#
- Handle Resolution: Converts handle to DID via
.well-known/atproto-did - DID Document Fetch: Retrieves user's DID document to find PDS
- Auth Server Discovery: Discovers OAuth server via PDS metadata
- PKCE Generation: Creates code verifier and S256 challenge
- PAR Request: Pushes authorization parameters to server
- User Authorization: Opens ASWebAuthenticationSession for user consent
- Token Exchange: Exchanges authorization code for tokens with PKCE
- Identity Verification: Verifies DID in token matches expected user
Security Features#
- PKCE with S256: Prevents authorization code interception
- DPoP: Binds tokens to specific key pairs to prevent replay attacks
- State Parameter: Prevents CSRF attacks
- Keychain Storage: All tokens stored securely in iOS Keychain
- ASWebAuthenticationSession: Uses system browser for OAuth (not embedded WebView)
Development Mode#
The app runs in development mode by default (useDevelopmentMode = true in Constants.swift), which uses http://localhost as the client ID. This is allowed by ATProtocol for development purposes.
For production:
- Set
useDevelopmentMode = false - Host a
client-metadata.jsonfile atclientMetadataURL - Update
clientMetadataURLin Constants.swift
Customization#
Change URL Scheme#
- Update
urlSchemeinConstants.swift - Update
CFBundleURLSchemesinInfo.plist - Ensure both match your bundle identifier
Add More XRPC Methods#
Add new methods to XRPCClient.swift following the same pattern:
func yourMethod() async throws -> YourResponse {
// Similar structure to createPost()
}
Troubleshooting#
"Sign-in was cancelled"#
- User cancelled the OAuth flow
- Check that the handle is valid
"Identity verification failed"#
- DID mismatch between expected and actual
- Try signing out and signing in again
"Could not obtain access tokens"#
- Check network connection
- Ensure handle is a valid Bluesky account
- Check Xcode console for detailed errors
URL Scheme Issues#
- Ensure Info.plist URL scheme matches Constants.swift
- Verify the scheme is unique (use your bundle ID)
- Check that OAuth callback URL in logs matches your redirect URI
Testing#
Test with a real Bluesky account:
- Create a free account at https://bsky.app
- Use your handle (e.g., "yourname.bsky.social") in the app
- Verify authentication completes successfully
- Test creating a post and check it appears on Bluesky
Architecture#
This app uses:
- SwiftUI for the user interface
- MVVM pattern with
AuthenticationManageras ViewModel - Combine for reactive state management
- async/await for asynchronous operations
- No external dependencies - uses only native iOS frameworks
Resources#
License#
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing#
This is a reference implementation. Feel free to use it as a starting point for your own ATProtocol applications.