Moya: Refreshing auth token confusion

Created on 5 Mar 2019  路  7Comments  路  Source: Moya/Moya

I replied in #744, but I continue to experience difficulties trying to sort this out, so I've made a new thread.

I need to be able to pass a bearer token (for some of my calls) and refresh it when necessary. Currently, I have the following class to handle all of my API calls:

import Foundation
import Moya

enum ApiService {
    case signIn(email: String, password: String)
    case like(id: Int, type: String)
}

extension ApiService: TargetType, AccessTokenAuthorizable {
    var authorizationType: AuthorizationType {
        switch self {
        case .signIn(_, _):
            return .basic
        case .like(_, _):
            return .bearer
        }
    }

    var baseURL: URL {
        return URL(string: Constants.apiUrl)!
    }

    var path: String {
        switch self {
            case .signIn(_, _):
                return "user/signin"
            case .like(_, _):
                return "message/like"
        }
    }

    var method: Moya.Method {
        switch self {
            case .signIn, .like:
                return .post
        }
    }

    var task: Task {
        switch self {
            case let .signIn(email, password):
                return .requestParameters(parameters: ["email": email, "password": password], encoding: JSONEncoding.default)
            case let .like(id, type):
                return .requestParameters(parameters: ["messageId": id, "type": type], encoding: JSONEncoding.default)
        }
    }

    var sampleData: Data {
        return Data()
    }

    var headers: [String: String]? {
        return ["Content-type": "application/json"]
    }
}

private extension String {
    var urlEscaped: String {
        return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }

    var utf8Encoded: Data {
        return data(using: .utf8)!
    }
}

As @pietrocaselani suggested, here, I have tried to make sense of how to implement this in accordance with my above code, but I've been stuck now for many days.

Here is what that class looks like right now:

import Foundation
import Moya

public final class ApiProvider {
    private var token: String = ""

    private func isTokenValid() -> Bool {
        // check expiration date
        return false
    }

    func createProvider() -> MoyaProvider<ApiService> {
        let requestClosure = createRequestClosure()

        return MoyaProvider<ApiService>(requestClosure: requestClosure)
    }

    func createRequestClosure() -> MoyaProvider<ApiService>.RequestClosure {
        let requestClosure = { [unowned self] (endpoint: Endpoint, done: @escaping MoyaProvider.RequestResultClosure) in
            guard let request = try? endpoint.urlRequest() else {
                done(.failure(MoyaError.requestMapping(endpoint.url)))
                return
            }

            if (self.isTokenValid()) {
                done(.success(request))
                return
            }

            // How do I make this request from this class?
            provider.request(.refreshAuthToken()) { result in
                switch result {
                case .success(let response):
                    print("Success")
                    done(.success(request))
                case .failure(let error):
                    print("Error")
                    done(.failure(error))
                }
            }
        }

        return requestClosure
    }
}

However, I'm unsure of a few things:

  1. How do I make the request to refresh the auth token in the class? provider.request is obviously wrong, and self.request does not work either.
  2. How would I use this class in other classes (for example, controllers) to make my calls? What if I wanted to make a call that _doesn't_ require a bearer token?
stale

Most helpful comment

Hi @user6724161

I see that you are not using AccessTokenPlugin, I may have forgotten to add this on the other issue.

I made a small but fully functional project now. Please take a look at TVDBClient https://github.com/pietrocaselani/Moya-RefreshToken-Example

All 7 comments

Hi @user6724161

I see that you are not using AccessTokenPlugin, I may have forgotten to add this on the other issue.

I made a small but fully functional project now. Please take a look at TVDBClient https://github.com/pietrocaselani/Moya-RefreshToken-Example

This issue has been marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

This issue has been auto-closed because there hasn't been any activity for at least 21 days. However, we really appreciate your contribution, so thank you for that! 馃檹 Also, feel free to open a new issue if you still experience this problem 馃憤.

@pietrocaselani

I see that you are doing:

// If we are authenticating, skip all logic to get or refresh token, so we return the default request mapping
if target is Authentication.Type { return MoyaProvider<T>.defaultRequestMapping }

In the request closure, but I think you should also include using the default request mapping if the authorization type is .none. How would this be done?

@user6724161 +1 did you manage to do that?

@pietrocaselani how can to do what @user6724161 spoke?

@pietrocaselani

I see that you are doing:

// If we are authenticating, skip all logic to get or refresh token, so we return the default request mapping
if target is Authentication.Type { return MoyaProvider<T>.defaultRequestMapping }

In the request closure, but I think you should also include using the default request mapping if the authorization type is .none. How would this be done?

I replied in #744, but I continue to experience difficulties trying to sort this out, so I've made a new thread.

I need to be able to pass a bearer token (for some of my calls) and refresh it when necessary. Currently, I have the following class to handle all of my API calls:

import Foundation
import Moya

enum ApiService {
    case signIn(email: String, password: String)
    case like(id: Int, type: String)
}

extension ApiService: TargetType, AccessTokenAuthorizable {
    var authorizationType: AuthorizationType {
        switch self {
        case .signIn(_, _):
            return .basic
        case .like(_, _):
            return .bearer
        }
    }

    var baseURL: URL {
        return URL(string: Constants.apiUrl)!
    }

    var path: String {
        switch self {
            case .signIn(_, _):
                return "user/signin"
            case .like(_, _):
                return "message/like"
        }
    }

    var method: Moya.Method {
        switch self {
            case .signIn, .like:
                return .post
        }
    }

    var task: Task {
        switch self {
            case let .signIn(email, password):
                return .requestParameters(parameters: ["email": email, "password": password], encoding: JSONEncoding.default)
            case let .like(id, type):
                return .requestParameters(parameters: ["messageId": id, "type": type], encoding: JSONEncoding.default)
        }
    }

    var sampleData: Data {
        return Data()
    }

    var headers: [String: String]? {
        return ["Content-type": "application/json"]
    }
}

private extension String {
    var urlEscaped: String {
        return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }

    var utf8Encoded: Data {
        return data(using: .utf8)!
    }
}

As @pietrocaselani suggested, here, I have tried to make sense of how to implement this in accordance with my above code, but I've been stuck now for many days.

Here is what that class looks like right now:

import Foundation
import Moya

public final class ApiProvider {
    private var token: String = ""

    private func isTokenValid() -> Bool {
        // check expiration date
        return false
    }

    func createProvider() -> MoyaProvider<ApiService> {
        let requestClosure = createRequestClosure()

        return MoyaProvider<ApiService>(requestClosure: requestClosure)
    }

    func createRequestClosure() -> MoyaProvider<ApiService>.RequestClosure {
        let requestClosure = { [unowned self] (endpoint: Endpoint, done: @escaping MoyaProvider.RequestResultClosure) in
            guard let request = try? endpoint.urlRequest() else {
                done(.failure(MoyaError.requestMapping(endpoint.url)))
                return
            }

            if (self.isTokenValid()) {
                done(.success(request))
                return
            }

            // How do I make this request from this class?
            provider.request(.refreshAuthToken()) { result in
                switch result {
                case .success(let response):
                    print("Success")
                    done(.success(request))
                case .failure(let error):
                    print("Error")
                    done(.failure(error))
                }
            }
        }

        return requestClosure
    }
}

However, I'm unsure of a few things:

  1. How do I make the request to refresh the auth token in the class? provider.request is obviously wrong, and self.request does not work either.
  2. How would I use this class in other classes (for example, controllers) to make my calls? What if I wanted to make a call that _doesn't_ require a bearer token?

@user6724161 Please let me know if you solved this and how. Thanks.

I replied in #744, but I continue to experience difficulties trying to sort this out, so I've made a new thread.
I need to be able to pass a bearer token (for some of my calls) and refresh it when necessary. Currently, I have the following class to handle all of my API calls:

import Foundation
import Moya

enum ApiService {
    case signIn(email: String, password: String)
    case like(id: Int, type: String)
}

extension ApiService: TargetType, AccessTokenAuthorizable {
    var authorizationType: AuthorizationType {
        switch self {
        case .signIn(_, _):
            return .basic
        case .like(_, _):
            return .bearer
        }
    }

    var baseURL: URL {
        return URL(string: Constants.apiUrl)!
    }

    var path: String {
        switch self {
            case .signIn(_, _):
                return "user/signin"
            case .like(_, _):
                return "message/like"
        }
    }

    var method: Moya.Method {
        switch self {
            case .signIn, .like:
                return .post
        }
    }

    var task: Task {
        switch self {
            case let .signIn(email, password):
                return .requestParameters(parameters: ["email": email, "password": password], encoding: JSONEncoding.default)
            case let .like(id, type):
                return .requestParameters(parameters: ["messageId": id, "type": type], encoding: JSONEncoding.default)
        }
    }

    var sampleData: Data {
        return Data()
    }

    var headers: [String: String]? {
        return ["Content-type": "application/json"]
    }
}

private extension String {
    var urlEscaped: String {
        return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }

    var utf8Encoded: Data {
        return data(using: .utf8)!
    }
}

As @pietrocaselani suggested, here, I have tried to make sense of how to implement this in accordance with my above code, but I've been stuck now for many days.
Here is what that class looks like right now:

import Foundation
import Moya

public final class ApiProvider {
    private var token: String = ""

    private func isTokenValid() -> Bool {
        // check expiration date
        return false
    }

    func createProvider() -> MoyaProvider<ApiService> {
        let requestClosure = createRequestClosure()

        return MoyaProvider<ApiService>(requestClosure: requestClosure)
    }

    func createRequestClosure() -> MoyaProvider<ApiService>.RequestClosure {
        let requestClosure = { [unowned self] (endpoint: Endpoint, done: @escaping MoyaProvider.RequestResultClosure) in
            guard let request = try? endpoint.urlRequest() else {
                done(.failure(MoyaError.requestMapping(endpoint.url)))
                return
            }

            if (self.isTokenValid()) {
                done(.success(request))
                return
            }

            // How do I make this request from this class?
            provider.request(.refreshAuthToken()) { result in
                switch result {
                case .success(let response):
                    print("Success")
                    done(.success(request))
                case .failure(let error):
                    print("Error")
                    done(.failure(error))
                }
            }
        }

        return requestClosure
    }
}

However, I'm unsure of a few things:

  1. How do I make the request to refresh the auth token in the class? provider.request is obviously wrong, and self.request does not work either.
  2. How would I use this class in other classes (for example, controllers) to make my calls? What if I wanted to make a call that _doesn't_ require a bearer token?

@user6724161 Please let me know if you solved this and how. Thanks.

How about provider.request(.refreshAuthToken()) to MoyaProvider<ApiService>().request(.refreshAuthToken()) ?

Was this page helpful?
0 / 5 - 0 ratings