Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 token and retry the previous API calls. I'm storing the authentication token once I logged in successfully. So after login, in each API, I'm appending the token in the header part. And when if the token is expired I will get 401. That time I need to auto-refresh the authentication token and recall the same API again. How can I do that? I checked in the Stackoverflow, but I didn't get any solution.

Here's my API Call,

import Foundation
import Alamofire
import SwiftyJSON

class LoveltyAPI {

    let loveltyURL = Bundle.main.object(forInfoDictionaryKey: "APIUrlString") as! String  // Main URL
    let buildVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String  //infoDictionary?["CFBundleShortVersionString"] as AnyObject
    weak var delegate:LoveltyProtocol?  

    func get_profile(app_user_id:String, token:String) {
        let urlString = "\(loveltyURL)\(get_profile_string)?app_user_id=\(app_user_id)"
        let headers = ["Content-Type":"application/json","X-Requested-With":"XMLHttpRequest", "Authentication":"Token \(token)"]
        Alamofire.request(urlString, method: .get, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
            switch response.result {
            case .success:
                let swiftyJsonVar = JSON(response.result.value!)
                switch response.response?.statusCode {
                case 200, 201:
                    self.delegate?.getUserProfile!(response: swiftyJsonVar["data"].dictionaryObject as AnyObject)
                case 401:
                    self.delegate?.tokenExpired(response: tokenExpired as AnyObject)
                case 404:
                    self.delegate?.serviceError!(response: swiftyJsonVar["message"] as AnyObject)
                case 422:
                    self.delegate?.serviceError!(response: swiftyJsonVar["error"] as AnyObject)
                case 503:
                    self.delegate?.appDisabled(response: swiftyJsonVar.dictionaryObject as AnyObject)
                default:
                    self.delegate?.serviceError!(response: self.serverError as AnyObject)
                }
            case .failure(let error):
                self.delegate?.serviceError!(response: self.serverError as AnyObject)
            }
        }
    }
}

Please help me. If you can explain with my code, it would be very nice.

like image 634
Hilaj Avatar asked Jul 10 '19 05:07

Hilaj


1 Answers

You can easily Refresh token and retry your previous API call using

Alamofire RequestInterceptor

NetworkManager.swift

import Alamofire
    class NetworkManager {
        static let shared: NetworkManager = {
            return NetworkManager()
        }()
        typealias completionHandler = ((Result<Data, CustomError>) -> Void)
        var request: Alamofire.Request?
        let retryLimit = 3
        
        func request(_ url: String, method: HTTPMethod = .get, parameters: Parameters? = nil,
                     encoding: ParameterEncoding = URLEncoding.queryString, headers: HTTPHeaders? = nil,
                     interceptor: RequestInterceptor? = nil, completion: @escaping completionHandler) {
            AF.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers, interceptor: interceptor ?? self).validate().responseJSON { (response) in
                if let data = response.data {
                    completion(.success(data))
                } else {
                    completion(.failure())
                }
            }
        }
        
    }

RequestInterceptor.swift

  import Alamofire
extension NetworkManager: RequestInterceptor {
    
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var request = urlRequest
        guard let token = UserDefaultsManager.shared.getToken() else {
            completion(.success(urlRequest))
            return
        }
        let bearerToken = "Bearer \(token)"
        request.setValue(bearerToken, forHTTPHeaderField: "Authorization")
        print("\nadapted; token added to the header field is: \(bearerToken)\n")
        completion(.success(request))
    }
    
    func retry(_ request: Request, for session: Session, dueTo error: Error,
               completion: @escaping (RetryResult) -> Void) {
       guard let statusCode = request.response?.statusCode else {
        completion(.doNotRetry)
        return
    }
    
    guard request.retryCount < retryLimit else {
        completion(.doNotRetry)
        return
    }
    print("retry statusCode....\(statusCode)")
    switch statusCode {
    case 200...299:
        completion(.doNotRetry)
    case 401:
        refreshToken { isSuccess in isSuccess ? completion(.retry) : completion(.doNotRetry) }
        break
    default:
        completion(.retry)
    } 
    }
    
    func refreshToken(completion: @escaping (_ isSuccess: Bool) -> Void) {
                let params = [
"refresh_token": Helpers.getStringValueForKey(Constants.REFRESH_TOKEN)
        ]
        AF.request(url, method: .post, parameters: params, encoding: JSONEncoding.default).responseJSON { response in
            if let data = response.data, let token = (try? JSONSerialization.jsonObject(with: data, options: [])
                as? [String: Any])?["access_token"] as? String {
                UserDefaultsManager.shared.setToken(token: token)
                print("\nRefresh token completed successfully. New token is: \(token)\n")
                completion(true)
            } else {
                completion(false)
            }
        }
    }
    
}

Alamofire v5 has a property named RequestInterceptor. RequestInterceptor has two method, one is Adapt which assign access_token to any Network call header, second one is Retry method. In Retry method we can check response status code and call refresh_token block to get new token and retry previous API again.

like image 114
Menon Hasan Avatar answered Sep 30 '22 03:09

Menon Hasan