Created
February 13, 2025 19:57
-
-
Save fahied/ddd6e40206af738cf3484c92fc48dea7 to your computer and use it in GitHub Desktop.
Bearer Token Renewal 401
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// A thread-safe actor responsible for managing the application's authentication token. | |
actor TokenManager { | |
/// The shared singleton instance of `TokenManager`. | |
static let shared = TokenManager() | |
/// The current authentication token. | |
private var token: String = "initial_token" | |
/// Retrieves the current authentication token. | |
/// - Returns: The current token as a `String`. | |
func getToken() -> String { | |
return token | |
} | |
/// Updates the authentication token with a new value. | |
/// - Parameter newToken: The new token to append to the current token. | |
func updateToken(_ newToken: String) { | |
token += newToken | |
print("token = \(token)") | |
} | |
} | |
/// A class responsible for handling authentication-related operations, such as token refresh. | |
/// | |
/// This class ensures that if multiple requests fail with a `401 Unauthorized` error simultaneously, | |
/// only one token refresh operation is performed. All other requests are queued and resumed | |
/// once the token is successfully renewed. | |
/// | |
/// ### Key Features: | |
/// - **Single Token Refresh**: Only one `reAuth` operation is performed, even if multiple `401` errors occur. | |
/// - **Queueing Requests**: Pending requests are stored in a queue and resumed after the token is refreshed. | |
/// - **Thread Safety**: Uses an `AsyncLock` to ensure thread-safe access to shared resources. | |
class Authenticator { | |
/// A lock to ensure that only one token refresh operation happens at a time. | |
private let refreshLock = AsyncLock() | |
/// Simulates a token refresh operation. | |
/// | |
/// This method sleeps for 2 seconds to simulate a network delay and then updates the token. | |
/// - Throws: An error if the token refresh fails. | |
private func reAuth() async throws { | |
print("Refreshing token...") | |
try await Task.sleep(nanoseconds: 2_000_000_000) // Simulate network delay | |
await TokenManager.shared.updateToken("new_token") | |
print("Token refreshed.") | |
} | |
/// A no-op method used as a placeholder when no operation is needed. | |
/// - Throws: An error if the operation fails. | |
func nothing() async throws {} | |
/// Handles a `401 Unauthorized` error by refreshing the token if necessary. | |
/// | |
/// This method ensures that only one token refresh operation happens at a time. | |
/// If the lock is already acquired (i.e., a token refresh is in progress), | |
/// the request is queued and resumed once the token is refreshed. | |
/// | |
/// ### Flow: | |
/// 1. Check if the lock is already acquired. | |
/// 2. If the lock is not acquired, initiate a token refresh (`reAuth`). | |
/// 3. If the lock is acquired, queue the request and wait for the token refresh to complete. | |
/// 4. Once the token is refreshed, resume all queued requests. | |
/// | |
/// - Throws: An error if the token refresh fails. | |
func handle401() async throws { | |
// Determine whether to call `reAuth` or `nothing` based on the lock state. | |
let ops = await refreshLock.isLocked ? nothing : reAuth | |
try await refreshLock.lock { | |
try await ops() // Ensures only one re-auth happens | |
} | |
} | |
} | |
/// A thread-safe actor that provides an asynchronous lock mechanism. | |
actor AsyncLock { | |
/// Indicates whether the lock is currently acquired. | |
var isLocked = false | |
/// A queue of continuations for tasks waiting to acquire the lock. | |
var waiters = [CheckedContinuation<Void, Never>]() | |
/// Acquires the lock, executes the given operation, and releases the lock. | |
/// - Parameter operation: The asynchronous operation to execute while holding the lock. | |
/// - Returns: The result of the operation. | |
/// - Throws: An error if the operation throws an error. | |
func lock<T>(_ operation: () async throws -> T) async throws -> T { | |
await acquire() // Acquire the lock | |
defer { release() } // Ensure the lock is released | |
return try await operation() // Execute the operation | |
} | |
/// Acquires the lock, suspending the task if the lock is already held. | |
private func acquire() async { | |
while isLocked { | |
// Suspend the task and add its continuation to the waiters queue. | |
await withCheckedContinuation { continuation in | |
waiters.append(continuation) | |
} | |
// Yield to allow other tasks to run. | |
await Task.yield() | |
} | |
isLocked = true // Mark the lock as acquired | |
} | |
/// Releases the lock and resumes the next task in the waiters queue. | |
private func release() { | |
// Resume all waiting tasks. | |
for waiter in waiters { | |
waiters.removeFirst() | |
waiter.resume() | |
} | |
isLocked = false // Mark the lock as released | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment