Test client auth options from libxmtp `cv/auth-ffi-bindings-ios-test`
This PR updates the iOS bindings to libxmtp version 4.6.1-rc2.
Changes:
- Updated XMTP.podspec version to 4.6.1-rc2
- Updated binary URLs in Package.swift to point to the new release
- Updated checksum in Package.swift
- Updated Swift source file (xmtpv3.swift) from the new release
Base branch: main
Claude finished @xmtptools's task —— View job
Code Review Complete ✅
Todo List
- [x] Read and analyze Auth.swift
- [x] Review Client.swift changes
- [x] Review AuthTests.swift for test coverage
- [x] Provide comprehensive feedback on code quality, bugs, performance, security, and testing
Review Summary
This PR adds authentication callback/handle support to the iOS SDK, enabling custom authentication flows for backend connections. The implementation is well-structured and follows Swift best practices. Below is my detailed analysis:
✅ Code Quality & Best Practices
Auth.swift (Sources/XMTPiOS/Auth.swift:1-76)
- Strong Swift patterns: Good use of protocol-oriented design with clean FFI bridging
- Well-organized structure: Clear separation between public API and internal wrappers
- Good naming:
Credential,AuthCallback,AuthHandleare clear and idiomatic - Proper use of
@Sendable: TheAuthCallbacktypealias correctly uses@Sendablefor concurrency safety - Nice convenience property:
expiresAtcomputed property (line 16-18) provides a user-friendlyDateinterface
Client.swift (Sources/XMTPiOS/Client.swift:89-91, 474-475, 501-502)
- Consistent integration: Auth parameters properly threaded through
ClientOptions.Apiand both API connection methods - Good cache key handling: The
ApiCacheKey.stringValue(line 160) notably does NOT include auth params, which is correct since auth state shouldn't affect caching - Minimal changes: Integration is non-invasive and backward compatible
⚠️ Potential Issues & Concerns
1. Concurrency Safety - @unchecked Sendable (Auth.swift:59)
private final class InternalAuthCallback: FfiAuthCallback, @unchecked Sendable {
let callback: AuthCallback
Issue: Using @unchecked Sendable bypasses Swift's concurrency safety checks. While AuthCallback is marked @Sendable, the class itself isn't proven to be thread-safe.
Recommendation: Consider documenting why @unchecked Sendable is necessary here (likely due to FFI protocol requirements). If the underlying FFI callback could be invoked from multiple threads simultaneously, there could be race conditions.
2. Weak Authentication Error Handling (Auth.swift:66-69)
func onAuthRequired() async throws -> FfiCredential {
let credential = try await callback()
return credential.ffi
}
Issue: If the callback throws, the error propagates directly without any context. This makes debugging auth failures difficult for SDK users.
Recommendation: Consider wrapping errors with context:
do {
let credential = try await callback()
return credential.ffi
} catch {
throw AuthError.callbackFailed(underlyingError: error)
}
3. No Credential Validation
Issue: The Credential struct (Auth.swift:5-19) accepts any values without validation:
expiresAtSecondscould be in the past or negativevaluecould be empty- No bounds checking
Recommendation: Add validation, especially for security-critical auth credentials:
public init(name: String?, value: String, expiresAtSeconds: Int64) throws {
guard !value.isEmpty else {
throw CredentialError.emptyValue
}
guard expiresAtSeconds > 0 else {
throw CredentialError.invalidExpiration
}
// ...
}
4. Optional Name Field - Unclear Semantics (Auth.swift:6)
public let name: Optional<String>
Question: When should name be nil vs an empty string vs a value? This isn't documented and could lead to inconsistent usage.
Recommendation: Add documentation explaining the semantics of the optional name field.
🔒 Security Considerations
1. Credential Storage & Lifetime
- Good: Credentials are value types, reducing risk of accidental mutation
- Concern: No guidance on secure storage. SDK users might log or persist credentials insecurely
- Recommendation: Add documentation warning against logging credential values
2. No Expiration Check
The SDK doesn't validate that credentials haven't expired before use. While this might be intentional (backend should validate), it could lead to unnecessary network calls with expired credentials.
3. AuthHandle Thread Safety
public class AuthHandle {
private let ffiHandle: FfiAuthHandle
public func set(_ credential: Credential) async throws {
try await ffiHandle.set(credential: credential.ffi)
}
}
Concern: Multiple concurrent calls to set() could cause race conditions if ffiHandle isn't thread-safe. The implementation doesn't use any synchronization.
Recommendation: Consider making AuthHandle an actor for proper thread safety, or document thread-safety requirements.
⚡ Performance Considerations
- Auth callback overhead: The wrapping layer (
InternalAuthCallback) adds minimal overhead - this is acceptable - API client caching: Correctly does NOT cache based on auth parameters, which is good since credentials change
🧪 Test Coverage
AuthTests.swift provides decent basic coverage but has gaps:
✅ Good Coverage
- Credential conversion (lines 6-24)
- Auth callback invocation (lines 26-54)
- AuthHandle usage (lines 56-76)
❌ Missing Test Coverage
-
Error handling paths:
- What happens when
authCallbackthrows? - How are FFI errors propagated?
- What happens when
-
Edge cases:
- Expired credentials (past
expiresAtSeconds) - Empty credential values
- Nil
namefield handling - Concurrent calls to
AuthHandle.set()
- Expired credentials (past
-
Integration scenarios:
- Using auth with real backend connection (tests use
.localenv) - Auth callback timing (when exactly is it invoked?)
- Multiple clients with different auth configs
- Using auth with real backend connection (tests use
-
Memory/concurrency:
- No tests for concurrent auth callback invocations
- No tests verifying
@Sendablecompliance
Recommendation: Add tests for:
func testAuthCallbackErrorPropagation() async throws {
let errorCallback: AuthCallback = {
throw TestError.authFailed
}
// Verify error is properly propagated
}
func testExpiredCredentials() async throws {
let expiredCred = Credential(
name: "test",
value: "value",
expiresAtSeconds: -1000 // Past
)
// Verify behavior with expired creds
}
func testConcurrentAuthHandleCalls() async throws {
// Test thread safety
}
📋 Additional Recommendations
-
Documentation: Add code comments explaining:
- When/how auth callbacks are invoked
- Thread-safety requirements for implementing callbacks
- Lifecycle of credentials (when they're cached, refreshed, etc.)
-
Consider adding a convenience initializer:
extension Credential {
public init(name: String?, value: String, expiresIn: TimeInterval) {
self.init(
name: name,
value: value,
expiresAtSeconds: Int64(Date().timeIntervalSince1970 + expiresIn)
)
}
}
- Error types: Consider defining explicit error types for auth failures rather than relying on generic FFI errors.
✨ Positive Highlights
- Clean, minimal API surface
- Good separation of concerns
- Backward compatible (all auth params are optional)
- Follows existing SDK patterns well
- FFI bridging is well-implemented
Overall Assessment: This is solid work that adds important authentication flexibility. The main concerns are around error handling, validation, and thread safety. With the recommended improvements, this would be production-ready. The test coverage is functional but could be more comprehensive around edge cases and error scenarios.
Add client auth options by wiring ClientOptions.Api authCallback and authHandle through to backend connections
Introduce Credential, AuthHandle, and FFI bridging, and pass ClientOptions.Api auth parameters to backend in Sources/XMTPiOS/Client.swift and Sources/XMTPiOS/Auth.swift.
📍Where to Start
Start with ClientOptions.Api handling and backend wiring in Sources/XMTPiOS/Client.swift, then review FFI bridging in Credential, AuthHandle, and makeInternalAuthCallback in Sources/XMTPiOS/Auth.swift.
Macroscope summarized 74c72cb.
@lourou @yewreeka latest commit is tagged here as 4.6.1-dev.933e598 for testing Authentication headers against the d14z gateway. Ping @tylerhawkes or me if you have any feedback thanks!