user6724161
user6724161

Reputation: 205

Refreshing bearer token and using AccessTokenPlugin

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:

  1. How would I modify my MoyaProvider extension to utilize Moya's AccessTokenPlugin, found here?
  2. How would I modify my MoyaProvider extension to NOT require a bearer token if the AuthorizationType is .none for a specific request?

Thanks!

Upvotes: 3

Views: 1523

Answers (1)

SPatel
SPatel

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

Related Questions