Alamofire auto refresh token and retry previous API call in iOS Swift 4

前端 未结 2 468
耶瑟儿~
耶瑟儿~ 2021-02-04 21:58

now I\'m working on an iOS application in Swift 4. Here I\'m using Alamofire to integrate the API calls. I need to integrate the right way to auto-refresh the authentication tok

2条回答
  •  星月不相逢
    2021-02-04 22:27

    You need Alamofire RequestRetrier and RequestAdapter check here

    This is some example that I have:

    import UIKit
    import Alamofire
    
    class MyRequestAdapter: RequestAdapter, RequestRetrier {
        private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?) -> Void
    
        private let lock = NSLock()
    
        private var isRefreshing = false
        private var requestsToRetry: [RequestRetryCompletion] = []
        var accessToken:String? = nil
        var refreshToken:String? = nil
        static let shared = MyRequestAdapter()
    
        private init(){
            let sessionManager = Alamofire.SessionManager.default
            sessionManager.adapter = self
            sessionManager.retrier = self
        }
    
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            var urlRequest = urlRequest
    
            if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(BASE_URL), !urlString.hasSuffix("/renew") {
                if let token = accessToken {
                    urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
                }
            }
            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 in
                        guard let strongSelf = self else { return }
    
                        strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
    
                        if let accessToken = accessToken {
                            strongSelf.accessToken = accessToken
                        }
    
                        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 = "\(BASE_URL)token/renew"
    
            Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: ["Authorization":"Bearer \(refreshToken!)"]).responseJSON { [weak self] response in
                guard let strongSelf = self else { return }
                if
                    let json = response.result.value as? [String: Any],
                    let accessToken = json["accessToken"] as? String
                {
                    completion(true, accessToken)
                } else {
                    completion(false, nil)
                }
                strongSelf.isRefreshing = false
            }
    
        }
    }
    

    My example is a little bit complex, but yes in general we have two important methods first one is adapt(_ urlRequest: URLRequest) throws -> URLRequest where we attaching the token, here I have custom logic where one of the services have should not attach this token as a header. The second method is func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) where I check what is the error code(in my example 401). And then I refresh my tokens with

     private func refreshTokens(completion: @escaping RefreshCompletion)
    

    In my case, I have refresh token and access token and when I call the service with refresh token I should not append my old access token in the header. I think this is not best practice but it was implemented from peopele that I don't know.

提交回复
热议问题