Search code examples
iosswiftoauthalamofireaccess-token

Alamofire 4 retrier and adaptor unable to see the changed accessToken


I am fetching new access token with alamofire's retrier and adapt protocol. I am able to fetch a new token but sometimes when another thread is calling the same method, it is not working and the request fails even when new access Token is generated.

I have just changed the example and now I am using synchronous request to fetch access Token as I don't want to send an extra request in adapt if I get to know token is invalid.

The strange issue is that when I print the response on the failed request, I see that request still had the old token in the header. What am I missing here?

func isTokenValid() -> Bool {
    return Date() < self.expiryTime
}

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
    var urlRequest = urlRequest
    urlRequest = processRequest(urlRequest: urlRequest)
    return urlRequest
}

func processRequest(urlRequest: URLRequest) -> URLRequest {
    DDLogInfo("******access token : \(self.accessToken)***************")
    DDLogInfo("***** expiry Time: \(self.expiryTime)***************")
    var urlRequest = urlRequest
    lock.lock(); defer {   DDLogInfo( "Thread UnLocked ⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
        lock.unlock()
    }
    DDLogInfo( "Thread Locked ⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")

    if !isTokenValid() {
        let _ = self.refreshAccessToken()
    }

    urlRequest = self.appendToken(urlRequest: urlRequest)
    DDLogInfo("here \(urlRequest)")
    return urlRequest
}

func appendToken(urlRequest: URLRequest) -> URLRequest {
    if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
        var urlRequest = urlRequest
        DDLogInfo("token appended : \(self.accessToken)")
        urlRequest.setValue(self.accessToken, forHTTPHeaderField: Constants.KeychainKeys.accessToken)
    }
    return urlRequest
}

// MARK: - RequestRetrier

 func handleFailedRequest(_ completion: @escaping RequestRetryCompletion) {
    requestsToRetry.append(completion)
    if !isRefreshing {
        lock.lock()
        print( "Thread Locked⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")

        let succeeded = self.refreshAccessToken()
        self.requestsToRetry.forEach {
            print("token fetched \(succeeded): \(self.accessToken)")
            $0(succeeded, 0.0)
        }
        self.requestsToRetry.removeAll()
        DDLogInfo( "Thread UnLocked⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
        lock.unlock()
    }
}


func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {

    if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401, request.retryCount < 3 {
        handleFailedRequest(completion)
    } else {
        completion(false, 0.0)
    }
}

func updateTokens (accessToken: String, refreshToken: String, accessTokenExpiresIn: Double) {
    self.accessToken = accessToken
    self.refreshToken = refreshToken
    let expiryDate = Date(timeIntervalSinceNow: accessTokenExpiresIn - Constants.KeychainKeys.expirationBuffer)
    AppSettings.sharedInstance.tokenExpiryTime = expiryDate
    self.expiryTime = expiryDate
    do {try keychainWrapper.save(values: ["accessToken": self.accessToken, "refreshToken": self.refreshToken])} catch {
        DDLogError("unable to save accessToken")
        }
}

// MARK: - Private - Refresh Tokens

  fileprivate func refreshAccessToken() -> Bool {
    DDLogInfo("^^^^^^^^")
    Thread.callStackSymbols.forEach {  DDLogInfo($0) }

    var success = false
    guard !isRefreshing else { return success }
    let refreshRequest = URLRequestConfigurations.configRefreshProviderAgent(refreshToken: self.refreshToken)
    let result = URLSession.shared.synchronousDataTask(with: refreshRequest)

    self.isRefreshing = false
    do {
        if let data = result.0, let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
            if let accessToken = json["accessToken"] as? String, let refreshToken = json["refreshToken"] as? String, let time = json["accessTokenExpiresIn"] as? Double {
                updateTokens(accessToken: accessToken, refreshToken: refreshToken, accessTokenExpiresIn: time)
                success = true
            } else {
                DDLogError("unable to find tokens/expiryInterval from refresh request")
            }
        } else {
            DDLogError("unable to receive data from refresh request")
        }
    } catch {
        DDLogError("unable to parse json response from refersh token request")
    }
    return success
}

Solution

  • I found the answer to the problem.

    In adapt method I had to use a different variable for urlRequest as it was not modifying the request when using the same variable name. As you can see below I changed the variable to "mutableRequest"

    func appendToken(urlRequest: URLRequest) -> URLRequest {
        var mutableRequest = urlRequest
        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
            DDLogInfo("token appended : \(self.accessToken)")
            mutableRequest.setValue(self.accessToken, forHTTPHeaderField: Constants.KeychainKeys.accessToken)
        }
        return mutableRequest
    }