Main coves client
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/)