There is a function getUser
in RequestManager class
that called in my VC
.
func getUser(onCompletion: @escaping (_ result: User?, error: String?) -> Void) {
Alamofire.request(Router.getUser).responseJSON { (response) in
// here is the work with response
}
}
If this request returns 403
it means access_token
is expired. I need to refresh token and repeat the request from my VC
.
Now the question.
How to refresh token and repeat the request in the right way?
To handle the error and refresh token in MyViewController
or getUser
method is not good idea because I have a lot of VCs
and request methods
.
I need something like: VC
calls the method and gets the User
even if token is expired and refreshToken
must not be in all request methods.
EDIT
refreshToken
method
func refreshToken(onCompletion: @escaping (_ result: Bool?) -> Void) {
Alamofire.request(Router.refreshToken).responseJSON { (response) in
print(response)
if response.response?.statusCode == 200 {
guard let data = response.data else { return onCompletion(false) }
let token = try? JSONDecoder().decode(Token.self, from: data)
token?.setToken()
onCompletion(true)
} else {
onCompletion(false)
}
}
}
The main purpose of using a refresh token is to considerably shorten the life of an access token. The refresh token can then later be used to authenticate the user as and when required by the application without running into problems such as cookies being blocked, etc.
To refresh your access token as well as an ID token, you send a token request with a grant_type of refresh_token . Be sure to include the openid scope when you want to refresh the ID token. If the refresh token is valid, then you get back a new access and the refresh token.
Since access tokens aren't valid for an extended period because of security reasons, a refresh token helps to re-authenticate a user without login credentials. This Refresh token is never exposed to the client-side Javascript, even if our access token gets compromised it'll be expired in a very short duration.
The refresh token is set with a very long expiration time of 200 days. If the traffic to this API is 10 requests/second, then it can generate as many as 864,000 tokens in a day.
To solve this, I created a class from which we will call every API, say BaseService.swift
.
BaseService.swift :
import Foundation
import Alamofire
import iComponents
struct AlamofireRequestModal {
var method: Alamofire.HTTPMethod
var path: String
var parameters: [String: AnyObject]?
var encoding: ParameterEncoding
var headers: [String: String]?
init() {
method = .get
path = ""
parameters = nil
encoding = JSONEncoding() as ParameterEncoding
headers = ["Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Cache-Control": "no-cache"]
}
}
class BaseService: NSObject {
func callWebServiceAlamofire(_ alamoReq: AlamofireRequestModal, success: @escaping ((_ responseObject: AnyObject?) -> Void), failure: @escaping ((_ error: NSError?) -> Void)) {
// Create alamofire request
// "alamoReq" is overridden in services, which will create a request here
let req = Alamofire.request(alamoReq.path, method: alamoReq.method, parameters: alamoReq.parameters, encoding: alamoReq.encoding, headers: alamoReq.headers)
// Call response handler method of alamofire
req.validate(statusCode: 200..<600).responseJSON(completionHandler: { response in
let statusCode = response.response?.statusCode
switch response.result {
case .success(let data):
if statusCode == 200 {
Logs.DLog(object: "\n Success: \(response)")
success(data as AnyObject?)
} else if statusCode == 403 {
// Access token expire
self.requestForGetNewAccessToken(alaomReq: alamoReq, success: success, failure: failure)
} else {
let errorDict: [String: Any] = ((data as? NSDictionary)! as? [String: Any])!
Logs.DLog(object: "\n \(errorDict)")
failure(errorTemp as NSError?)
}
case .failure(let error):
Logs.DLog(object: "\n Failure: \(error.localizedDescription)")
failure(error as NSError?)
}
})
}
}
extension BaseService {
func getAccessToken() -> String {
if let accessToken = UserDefaults.standard.value(forKey: UserDefault.userAccessToken) as? String {
return "Bearer " + accessToken
} else {
return ""
}
}
// MARK: - API CALL
func requestForGetNewAccessToken(alaomReq: AlamofireRequestModal, success: @escaping ((_ responseObject: AnyObject?) -> Void), failure: @escaping ((_ error: NSError?) -> Void) ) {
UserModal().getAccessToken(success: { (responseObj) in
if let accessToken = responseObj?.value(forKey: "accessToken") {
UserDefaults.standard.set(accessToken, forKey: UserDefault.userAccessToken)
}
// override existing alaomReq (updating token in header)
var request: AlamofireRequestModal = alaomReq
request.headers = ["Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Cache-Control": "no-cache",
"X-Authorization": self.getAccessToken()]
self.callWebServiceAlamofire(request, success: success, failure: failure)
}, failure: { (_) in
self.requestForGetNewAccessToken(alaomReq: alaomReq, success: success, failure: failure)
})
}
}
For calling the API from this call, we need to create a object of AlamofireRequestModal
and override it with necessary parameter.
For example I created a file APIService.swift
in which we have a method for getUserProfileData
.
APIService.swift :
import Foundation
let GET_USER_PROFILE_METHOD = "user/profile"
struct BaseURL {
// Local Server
static let urlString: String = "http://192.168.10.236: 8084/"
// QAT Server
// static let urlString: String = "http://192.171.286.74: 8080/"
static let staging: String = BaseURL.urlString + "api/v1/"
}
class APIService: BaseService {
func getUserProfile(success: @escaping ((_ responseObject: AnyObject?) -> Void), failure: @escaping ((_ error: NSError?) -> Void)) {
var request: AlamofireRequestModal = AlamofireRequestModal()
request.method = .get
request.path = BaseURL.staging + GET_USER_PROFILE_METHOD
request.headers = ["Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Cache-Control": "no-cache",
"X-Authorization": getAccessToken()]
self.callWebServiceAlamofire(request, success: success, failure: failure)
}
}
Explanation:
In code block:
else if statusCode == 403 {
// Access token expire
self.requestForGetNewAccessToken(alaomReq: alamoReq, success: success, failure: failure)
}
I call getNewAccessToken API (say refresh-token, in your case), with the request( it could be any request based from APIService.swift).
When we get new token I save it user-defaults then I will update the request( the one I am getting as a parameter in refresh-token API call), and will pass the success and failure block as it is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With