1# atProto Identity Resolution Layer 2 3## Overview 4 5This module implements the **critical identity resolution functionality** for atProto decentralization. It resolves atProto handles and DIDs to discover where user data is actually stored (their Personal Data Server). 6 7## Why This Matters 8 9**This is the most important code for decentralization in atProto.** 10 11Without this layer: 12- Apps hardcode `bsky.social` as the only server 13- Users can't use custom domains 14- Self-hosting is impossible 15- atProto becomes centralized 16 17With this layer: 18- ✅ Users host data on any PDS they choose 19- ✅ Custom domain handles work (e.g., `alice.example.com`) 20- ✅ Identity is portable (change PDS without losing DID) 21- ✅ True decentralization is achieved 22 23## Architecture 24 25### Resolution Flow 26 27``` 28Handle/DID Input 29 30Is it a DID? ──Yes──→ DID Resolution 31 ↓ ↓ 32 No DID Document 33 ↓ ↓ 34Handle Resolution Extract Handle 35 ↓ ↓ 36 DID Validate Handle ←→ DID 37 ↓ ↓ 38DID Resolution Return IdentityInfo 39 40DID Document 41 42Validate Handle in Doc 43 44Extract PDS URL 45 46Return IdentityInfo 47``` 48 49### Key Components 50 51#### 1. IdentityResolver 52Main interface for resolving identities. Use `AtprotoIdentityResolver` for the standard implementation. 53 54```dart 55final resolver = AtprotoIdentityResolver.withDefaults( 56 handleResolverUrl: 'https://bsky.social', 57); 58 59// Resolve to PDS URL (most common use case) 60final pdsUrl = await resolver.resolveToPds('alice.example.com'); 61 62// Get full identity info 63final info = await resolver.resolve('alice.example.com'); 64print('DID: ${info.did}'); 65print('Handle: ${info.handle}'); 66print('PDS: ${info.pdsUrl}'); 67``` 68 69#### 2. HandleResolver 70Resolves atProto handles (e.g., `alice.bsky.social`) to DIDs using XRPC. 71 72**Resolution Methods:** 73- XRPC: Uses `com.atproto.identity.resolveHandle` endpoint 74- DNS TXT record: Checks `_atproto.{handle}` (not implemented yet) 75- .well-known: Checks `https://{handle}/.well-known/atproto-did` (not implemented yet) 76 77Current implementation uses XRPC, which works for all handles. 78 79#### 3. DidResolver 80Resolves DIDs to DID documents. 81 82**Supported Methods:** 83- `did:plc`: Queries PLC directory (https://plc.directory) 84- `did:web`: Fetches from HTTPS URLs 85 86#### 4. DidDocument 87Represents a W3C DID document with atProto-specific helpers: 88- `extractPdsUrl()`: Gets the PDS endpoint 89- `extractNormalizedHandle()`: Gets the validated handle 90 91### Bi-directional Resolution 92 93For security, we enforce **bi-directional resolution**: 94 951. Handle → DID resolution must succeed 962. DID document must contain the original handle 973. Both directions must agree 98 99This prevents: 100- Handle hijacking 101- DID spoofing 102- MITM attacks 103 104### Caching 105 106Built-in caching with configurable TTLs: 107- **Handles**: 1 hour default (handles can change) 108- **DIDs**: 24 hours default (DID docs are more stable) 109 110Caching is automatic but can be bypassed with `noCache: true`. 111 112## File Structure 113 114``` 115identity/ 116├── constants.dart # atProto constants 117├── did_document.dart # DID document representation 118├── did_helpers.dart # DID validation utilities 119├── did_resolver.dart # DID → DID document resolution 120├── handle_helpers.dart # Handle validation utilities 121├── handle_resolver.dart # Handle → DID resolution 122├── identity_resolver.dart # Main resolver (orchestrates everything) 123├── identity_resolver_error.dart # Error types 124├── identity.dart # Public exports 125└── README.md # This file 126``` 127 128## Usage Examples 129 130### Basic Resolution 131 132```dart 133import 'package:atproto_oauth_flutter/src/identity/identity.dart'; 134 135final resolver = AtprotoIdentityResolver.withDefaults( 136 handleResolverUrl: 'https://bsky.social', 137); 138 139// Simple PDS lookup 140final pdsUrl = await resolver.resolveToPds('alice.bsky.social'); 141print('PDS: $pdsUrl'); 142``` 143 144### Custom Configuration 145 146```dart 147// With custom caching and PLC directory 148final resolver = AtprotoIdentityResolver.withDefaults( 149 handleResolverUrl: 'https://bsky.social', 150 plcDirectoryUrl: 'https://plc.directory/', 151 didCache: InMemoryDidCache(ttl: Duration(hours: 12)), 152 handleCache: InMemoryHandleCache(ttl: Duration(minutes: 30)), 153); 154``` 155 156### Manual Component Construction 157 158```dart 159// Build your own resolver with custom components 160final dio = Dio(); 161 162final didResolver = CachedDidResolver( 163 AtprotoDidResolver(dio: dio), 164); 165 166final handleResolver = CachedHandleResolver( 167 XrpcHandleResolver('https://bsky.social', dio: dio), 168); 169 170final resolver = AtprotoIdentityResolver( 171 didResolver: didResolver, 172 handleResolver: handleResolver, 173); 174``` 175 176### Error Handling 177 178```dart 179try { 180 final info = await resolver.resolve('invalid-handle'); 181} on InvalidHandleError catch (e) { 182 print('Invalid handle format: $e'); 183} on HandleResolverError catch (e) { 184 print('Handle resolution failed: $e'); 185} on DidResolverError catch (e) { 186 print('DID resolution failed: $e'); 187} on IdentityResolverError catch (e) { 188 print('Identity resolution failed: $e'); 189} 190``` 191 192## Implementation Notes 193 194### Ported from TypeScript 195 196This implementation is a 1:1 port from the official atProto TypeScript packages: 197- `@atproto-labs/identity-resolver` 198- `@atproto-labs/did-resolver` 199- `@atproto-labs/handle-resolver` 200 201Source: `/home/bretton/Code/atproto/packages/oauth/oauth-client/src/identity-resolver.ts` 202 203### Differences from TypeScript 204 2051. **No DNS Resolution**: Dart doesn't have built-in DNS TXT lookups. We use XRPC only. 2062. **Simplified Caching**: In-memory only (TypeScript has more cache backends). 2073. **Dio instead of Fetch**: Using Dio HTTP client instead of global fetch. 2084. **Explicit Types**: Dart's type system is more explicit than TypeScript's. 209 210### Future Improvements 211 212- [ ] Add DNS-over-HTTPS for handle resolution 213- [ ] Implement .well-known handle resolution 214- [ ] Add persistent cache backends (SQLite, Hive) 215- [ ] Support custom DID methods beyond plc/web 216- [ ] Add metrics and observability 217- [ ] Implement resolver timeouts and retries 218 219## Testing 220 221Test the implementation with real handles: 222 223```dart 224// Test custom PDS 225final pds1 = await resolver.resolveToPds('bretton.dev'); 226assert(pds1.contains('pds.bretton.dev')); 227 228// Test Bluesky user 229final pds2 = await resolver.resolveToPds('pfrazee.com'); 230print('Paul Frazee PDS: $pds2'); 231 232// Test from DID 233final info = await resolver.resolveFromDid('did:plc:ragtjsm2j2vknwkz3zp4oxrd'); 234assert(info.handle == 'pfrazee.com'); 235``` 236 237## Security Considerations 238 2391. **Bi-directional Validation**: Always enforced to prevent spoofing 2402. **HTTPS Only**: All HTTP requests use HTTPS (except localhost for testing) 2413. **No Redirects**: HTTP redirects are rejected to prevent attacks 2424. **Input Validation**: All handles and DIDs are validated before use 2435. **Cache Poisoning**: TTLs prevent stale data, noCache option available 244 245## Performance 246 247Typical resolution times (with cold cache): 248- Handle → PDS: ~200-500ms (1 handle lookup + 1 DID fetch) 249- DID → PDS: ~100-200ms (1 DID fetch only) 250- Cached resolution: <1ms (in-memory lookup) 251 252For production apps: 253- Enable caching (default) 254- Use connection pooling (Dio does this) 255- Consider warming cache for known users 256- Monitor resolver errors and timeouts 257 258## References 259 260- [atProto DID Spec](https://atproto.com/specs/did) 261- [atProto Handle Spec](https://atproto.com/specs/handle) 262- [W3C DID Core](https://www.w3.org/TR/did-core/) 263- [PLC Directory](https://plc.directory/)