Reputation: 205
For many of my endpoints, I require a bearer token to be passed with the request to authenticate the user. Because of that, I also need to refresh the user's token if it expires.
I found this question to start me along, but I'm still unsure about some things and how to implement them.
As the answer to the question above suggests, I've created an extension for MoyaProvider
:
extension MoyaProvider {
convenience init(handleRefreshToken: Bool) {
if handleRefreshToken {
self.init(requestClosure: MoyaProvider.endpointResolver())
} else {
self.init()
}
}
static func endpointResolver() -> MoyaProvider<Target>.RequestClosure {
return { (endpoint, closure) in
//Getting the original request
let request = try! endpoint.urlRequest()
//assume you have saved the existing token somewhere
if (#tokenIsNotExpired#) {
// Token is valid, so just resume the original request
closure(.success(request))
return
}
//Do a request to refresh the authtoken based on refreshToken
authenticationProvider.request(.refreshToken(params)) { result in
switch result {
case .success(let response):
let token = response.mapJSON()["token"]
let newRefreshToken = response.mapJSON()["refreshToken"]
//overwrite your old token with the new token
//overwrite your old refreshToken with the new refresh token
closure(.success(request)) // This line will "resume" the actual request, and then you can use AccessTokenPlugin to set the Authentication header
case .failure(let error):
closure(.failure(error)) //something went terrible wrong! Request will not be performed
}
}
}
}
I've also created an extension for TargetType
, which looks like this:
import Moya
public protocol TargetTypeExtension: TargetType, AccessTokenAuthorizable {}
public extension TargetTypeExtension {
var baseURL: URL { return URL(string: Constants.apiUrl)! }
var headers: [String: String]? { return nil }
var method: Moya.Method { return .get }
var authorizationType: AuthorizationType { return .bearer }
var sampleData: Data { return Data() }
}
Here is one such implementation of it:
import Moya
public enum Posts {
case likePost(postId: Int)
case getComments(postId: Int)
}
extension Posts: TargetTypeExtension {
public var path: String {
switch self {
case .likePost(_): return "posts/like"
case .getComments(_): return "posts/comments"
}
}
public var authorizationType: AuthorizationType {
switch self {
case .likePost(_): return .bearer
case .getComments(_): return .none
}
}
public var method: Moya.Method {
switch self {
case .likePost, .getComments:
return .post
}
}
public var task: Task {
switch self {
case let .likePost(postId):
return .requestParameters(parameters: ["postId": postId], encoding: JSONEncoding.default)
case let .getComments(postId):
return .requestParameters(parameters: ["postId": postId], encoding: JSONEncoding.default)
}
}
}
As you can see, my likePost
request requires a bearer token, while my getComments
request does not.
So I have a few questions:
MoyaProvider
extension to utilize Moya's AccessTokenPlugin
, found here?MoyaProvider
extension to NOT require a bearer token if the AuthorizationType
is .none
for a specific request?Thanks!
Upvotes: 3
Views: 1523
Reputation: 4946
I did with custom moya provider and RxSwift if you are using RxSwift you can take reference, it will not directly fit into yor code!!
extension Reactive where Base: MyMoyaProvider {
func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Single<Response> {
return Single.create { [weak base] single in
var pandingCancelable:Cancellable?
/// reschedule original request
func reschedulCurrentRequest() {
base?.saveRequest({
pandingCancelable = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case.success(let response):
single(.success(response))
case .failure(let error):
single(.error(error))
}
}
})
}
func refreshAccessToken(refreshToken:String) -> Cancellable? {
base?.isRefreshing = true /// start retrieveing refresh token
return base?.request(.retriveAccessToken(refreshToken: refreshToken), completion: { _ in
base?.isRefreshing = false /// end retrieveing refresh token
base?.executeAllSavedRequests()
})
}
if (base?.isRefreshing ?? false) {
reschedulCurrentRequest()
return Disposables.create { pandingCancelable?.cancel() }
}
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case let .success(response):
if !(base?.isRefreshing ?? false), !token.isOAuth2TokenRefreshType, response.statusCode == TokenPlugin.CODE_UNAUTHORIZED, let refreshToken = token.getRefreshToken {
reschedulCurrentRequest()
_ = refreshAccessToken(refreshToken: refreshToken)
} else if (base?.isRefreshing ?? false) {
reschedulCurrentRequest()
} else {
single(.success(response))
}
case let .failure(error):
single(.error(error))
}
}
return Disposables.create {
cancellableToken?.cancel()
}
}
}
}
CustomProvider:
class MyMoyaProvider: MoyaProvider<MyServices> {
var isRefreshing = false
private var pandingRequests: [DispatchWorkItem] = []
func saveRequest(_ block: @escaping () -> Void) {
// Save request to DispatchWorkItem array
pandingRequests.append( DispatchWorkItem {
block()
})
}
func executeAllSavedRequests() {
pandingRequests.forEach({ DispatchQueue.global().async(execute: $0); })
pandingRequests.removeAll()
}
}
extension TargetType {
var isOAuth2TokenRefreshType: Bool { return false }
var getRefreshToken:String? { return nil }
}
Upvotes: 2