Alamofire 5 Adapting and Retrying Requests

10,507

Solution 1

Look at something like this.

 struct EnvironmentInterceptor: RequestInterceptor {

    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
        var adaptedRequest = urlRequest
        guard let token = AtraqService.shared.user?.token.accessToken else {
        completion(.success(adaptedRequest))
        return
        }
        adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        completion(.success(adaptedRequest))
     }

     func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
        //get token
        }
    }
}

Then

        Session(configuration: configuration,  interceptor: EnvironmentInterceptor())

Finally

request().validate().response...

Solution 2

If Alamofire 5 is not intercepting (adapting or retrying) your requests for some reason, then just try to check the delegate signature out as described here.

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (AFResult<URLRequest>) -> Void) {
    var modifiedURLRequest = urlRequest
    let apiToken = config.apiToken
    modifiedURLRequest.setValue(apiToken, forHTTPHeaderField: Constants.apiTokenHeader)
    completion(.success(modifiedURLRequest))
}

func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
    completion(.doNotRetry)
}

Here's the difference (look at the signature):

func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: @escaping (AFResult<URLRequest>) -> Void) {
    var modifiedURLRequest = urlRequest
    let apiToken = config.apiToken
    modifiedURLRequest.setValue(apiToken, forHTTPHeaderField: Constants.apiTokenHeader)
    completion(.success(modifiedURLRequest))
}

func retry(_ request: Alamofire.Request, for session: Alamofire.Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
    completion(.doNotRetry)
}
Share:
10,507

Related videos on Youtube

Ahsan Aasim
Author by

Ahsan Aasim

It’s not about ideas. It’s about making ideas happen.

Updated on September 15, 2022

Comments

  • Ahsan Aasim
    Ahsan Aasim over 1 year

    I am trying to implement my OAuth2 flow using Alamofire 5.0.0-beta.3. As i can see the documentation is still for Alamofire 4 as stated in the github page as well.

    I am trying to make the Oauth2 handler following the documentation for Alamofire 4. As the class names are changed, I am completely lost while making it.

    This is the code that i am following:

    class OAuth2Handler: RequestAdapter, RequestRetrier {
        private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void
    
        private let sessionManager: SessionManager = {
            let configuration = URLSessionConfiguration.default
            configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
    
            return SessionManager(configuration: configuration)
        }()
    
        private let lock = NSLock()
    
        private var clientID: String
        private var baseURLString: String
        private var accessToken: String
        private var refreshToken: String
    
        private var isRefreshing = false
        private var requestsToRetry: [RequestRetryCompletion] = []
    
        // MARK: - Initialization
    
        public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) {
            self.clientID = clientID
            self.baseURLString = baseURLString
            self.accessToken = accessToken
            self.refreshToken = refreshToken
        }
    
        // MARK: - RequestAdapter
    
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
                var urlRequest = urlRequest
                urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
                return urlRequest
            }
    
            return urlRequest
        }
    
        // MARK: - RequestRetrier
    
        func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
            lock.lock() ; defer { lock.unlock() }
    
            if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
                requestsToRetry.append(completion)
    
                if !isRefreshing {
                    refreshTokens { [weak self] succeeded, accessToken, refreshToken in
                        guard let strongSelf = self else { return }
    
                        strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
    
                        if let accessToken = accessToken, let refreshToken = refreshToken {
                            strongSelf.accessToken = accessToken
                            strongSelf.refreshToken = refreshToken
                        }
    
                        strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                        strongSelf.requestsToRetry.removeAll()
                    }
                }
            } else {
                completion(false, 0.0)
            }
        }
    
        // MARK: - Private - Refresh Tokens
    
        private func refreshTokens(completion: @escaping RefreshCompletion) {
            guard !isRefreshing else { return }
    
            isRefreshing = true
    
            let urlString = "\(baseURLString)/oauth2/token"
    
            let parameters: [String: Any] = [
                "access_token": accessToken,
                "refresh_token": refreshToken,
                "client_id": clientID,
                "grant_type": "refresh_token"
            ]
    
            sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
                .responseJSON { [weak self] response in
                    guard let strongSelf = self else { return }
    
                    if 
                        let json = response.result.value as? [String: Any], 
                        let accessToken = json["access_token"] as? String, 
                        let refreshToken = json["refresh_token"] as? String 
                    {
                        completion(true, accessToken, refreshToken)
                    } else {
                        completion(false, nil, nil)
                    }
    
                    strongSelf.isRefreshing = false
                }
        }
    }
    

    This is how to use this for alamofire 4:

    let baseURLString = "https://some.domain-behind-oauth2.com"
    
    let oauthHandler = OAuth2Handler(
        clientID: "12345678",
        baseURLString: baseURLString,
        accessToken: "abcd1234",
        refreshToken: "ef56789a"
    )
    
    let sessionManager = SessionManager()
    sessionManager.adapter = oauthHandler
    sessionManager.retrier = oauthHandler
    
    let urlString = "\(baseURLString)/some/endpoint"
    
    sessionManager.request(urlString).validate().responseJSON { response in
        debugPrint(response)
    }
    

    This is the link i am following to implement this. https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests