Skip to content

Enhancement: Implement automatic retry logic for expired tokens #203

@leogdion

Description

@leogdion

Overview

Add automatic token refresh and retry when CloudKit returns authentication errors due to expired tokens.

Current Behavior

// Token expires during operation
let result = try await service.queryRecords(recordType: "Article")
// Throws: CloudKitError.unauthorized
// User must manually refresh token and retry

Proposed Behavior

// Automatic retry with token refresh
let result = try await service.queryRecords(recordType: "Article")
// If token expired:
//   1. Catches 401 Unauthorized
//   2. Refreshes token automatically
//   3. Retries operation transparently
// Returns result or throws non-auth error

Benefits

  • Improved reliability: Operations don't fail due to timing issues
  • Better UX: Fewer user-visible errors
  • Simplified code: No manual retry logic needed
  • Graceful degradation: Handles token expiration seamlessly

Implementation Approach

1. Add Retry Configuration

public struct RetryConfiguration {
    /// Maximum number of retry attempts
    public let maxAttempts: Int = 3

    /// Which errors trigger retry
    public let retryableErrors: Set<CloudKitError> = [.unauthorized]

    /// Delay between retries (exponential backoff)
    public let baseDelay: TimeInterval = 1.0
}

2. Add Retry Middleware

internal actor RetryMiddleware: ClientMiddleware {
    func intercept(
        _ request: HTTPRequest,
        next: (HTTPRequest) async throws -> HTTPResponse
    ) async throws -> HTTPResponse {
        for attempt in 1...maxAttempts {
            let response = try await next(request)

            if response.status == .unauthorized && attempt < maxAttempts {
                // Refresh token
                try await tokenManager.refreshToken()

                // Exponential backoff
                try await Task.sleep(for: .seconds(baseDelay * pow(2, attempt - 1)))

                continue // Retry
            }

            return response
        }
    }
}

3. Integrate with CloudKitService

public init(
    containerIdentifier: String,
    tokenManager: any TokenManaging,
    retryConfiguration: RetryConfiguration = .default
) {
    // Add retry middleware to client
}

Considerations

  • Which operations should retry? Only read operations or writes too?
  • Token refresh strategy: Different for WebAuthToken vs APIToken?
  • Logging: Should retries be logged for debugging?
  • Max attempts: What's reasonable? (suggest: 3)
  • Backoff strategy: Exponential, linear, or fixed?
  • Cancellation: Respect Task cancellation during retry delays?

Edge Cases

  • Token refresh fails → propagate error immediately
  • Multiple concurrent requests with expired token → coordinate refresh
  • Server-to-Server auth → different refresh mechanism
  • Rate limiting (429) → different retry strategy

Testing Strategy

  • Mock token expiration scenarios
  • Test concurrent request handling
  • Verify exponential backoff timing
  • Test cancellation during retry
  • Verify max attempts limit

Related

  • All authentication managers (APITokenManager, WebAuthTokenManager, etc.)
  • CloudKitService error handling
  • Middleware architecture

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions