we fixed similar issue using serial queue and semaphore
import Alamofire
class AccessTokenInterceptor: RequestInterceptor {
let auth = OAuthService()
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
auth.refreshToken { [weak self] result in
switch result {
case .success(_):
urlRequest.setValue(accessTokenType + (self?.auth.oauth2?.accessToken ?? ""), forHTTPHeaderField: "Authorization")
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue(token, forHTTPHeaderField: "Token")
completion(.success(urlRequest))
case .failure(let error):
switch error {
case .noRefreshToken:
self?.forceLogoutUser(error: error)
case .invalidGrant(_):
self?.forceLogoutUser(error: error)
default: ()
}
}
}
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
}
}
Here whenever getToken is called we add this in serial queue and using semaphore to ensure only one call is sent at a time.
import Foundation
import OAuth2
class OAuthService: NSObject {
private let serialQueue : DispatchQueue = DispatchQueue(label: "refreshToken.serial.queue")
private let semaphore : DispatchSemaphore = DispatchSemaphore(value: 0)
func getToken(completion: @escaping (Result<Bool, OAuth2Error>) -> ()) {
if let validAccessToken = oauth2?.hasUnexpiredAccessToken(), validAccessToken {
completion(.success(true))
semaphore.signal()
}else {
serialQueue.async {
self.refreshOAuth2Token(completion)
}
refreshOAuth2Token(completion)
}
semaphore.wait()
}
func refreshOAuth2Token(_ completion: @escaping(Result<Bool, OAuth2Error>) -> ()) {
oauth2?.clientConfig.secretInBody = true
oauth2?.doRefreshToken(callback: { [weak self] authParamters, error in
guard let self = self else { return }
self.semaphore.signal()
if let error = error {
completion(.failure(error))
} else {
completion(.success(true))
}
})
}
}