Moya: Basic Auth Plugin leads to dispatch of multiple request.

Created on 21 Nov 2017  路  13Comments  路  Source: Moya/Moya

We are using Moya in our App and our Quality-Engineer realized that each request which will be send through Moya using the BasicAuthCredential-Plugin actually results in two requests.
One without Credentials and one with Credentials.

I added Code of an Example that results in this behaviour.

If I add the the authorization header directly through the header property everything works as expected.

I tried to dive deeper into the code but I could not find the place where both requests will be sent.

We are using Moya 10.0.0 and Charles 3.1.2 for request analysis.

import UIKit
import Moya
import CryptoSwift 

private typealias EndpointClosure = (Test) -> Endpoint<Test>

enum Test: TargetType {
    var basicAuthCredentials : URLCredential {
        switch self {
        case .test(let username, let password):
            return URLCredential(user: username, password: password.sha1(), persistence: .none)
        }
    }

    var baseURL: URL {
        return URL(string: "http://foo.com")!
    }

    var path: String {
        return "/pathThatNeedsAuth"
    }

    var method: Moya.Method {
        return .get
    }

    var sampleData: Data {
        return Data()
    }

    var task: Task {
        return .requestPlain
    }

    var headers: [String : String]? {
        return nil
    }

    var parameters: [String: Any]? {
        return nil
    }

    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }

    var sampleResponseClosure: () -> EndpointSampleResponse {
        return { return EndpointSampleResponse.networkResponse(200, Data()) }
    }

    case test(username: String, password: String)

}

class ViewController: UIViewController {        
    @IBAction func request() {
        let basicAuthPlugin = ViewController.makeBasicAuthPlugin()
        let provider =  MoyaProvider<Test>(endpointClosure: ViewController.makeEndpointClosure(), plugins: [basicAuthPlugin])

        provider.request(.test(username: "[email protected]", password: "estateSmart#2017!")) { response in
            switch response {
            case .success(let value):
                print(value)
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }

    private static func makeEndpointClosure() -> EndpointClosure {
        return { (target: Test) -> Endpoint<Test> in

            let target = target
            let method = target.method
            let httpHeader = target.headers
            let url = target.baseURL.appendingPathComponent(target.path).absoluteString

            var targetTask = target.task
            if let parameters = target.parameters {
                targetTask = .requestParameters(parameters: parameters, encoding: target.parameterEncoding)
            }

            let endpoint = Endpoint<Test>(url: url, sampleResponseClosure: target.sampleResponseClosure, method: method, task: targetTask, httpHeaderFields: httpHeader)
            return endpoint
        }
    }

    private static func makeBasicAuthPlugin() -> CredentialsPlugin {
        let basicAuthPlugin = CredentialsPlugin { target -> URLCredential? in
            guard let target = target as? Test else {
                return nil
            }

            return target.basicAuthCredentials
        }
        return basicAuthPlugin
    }
}


bug? question

Most helpful comment

@SebastianBoldt I just noticed this same bug using Moya 11.0.2 and found this auto-closed issue. Did you by chance find a solution or work-around that you'd be willing to share?

All 13 comments

We really appreciate your QE's (and your) time to surface this bug, that you guys used Charles to make sure it was a legitimate issue, and then wrote a minimal reproducible example 馃槃

This sounds like a problem and we'd love to fix it. In my opinion, the easiest way to find the cause and fix the bug is through TDD - we can write a failing test that shows the 2 request behavior, and then mess with the code until the test passes.

We'd be more than happy to accept a PR from you, or if you wait a couple days/longer, a contributor will probably come around and try to fix it. If you aren't sure where to start, write the failing test. That would be a huge help, no pressure though, we'll get around to it soon if not 馃檭

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 馃憤.

@SebastianBoldt I just noticed this same bug using Moya 11.0.2 and found this auto-closed issue. Did you by chance find a solution or work-around that you'd be willing to share?

@robfeldmann I solved it by not using the basic-auth plugin and adding the auth header properties directly into the header fields inside the endpoint closure.

Could any of you guys create an example project that I could look at? @SebastianBoldt @robfeldmann or maybe someone else with the same problem? Would help a lot.

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 馃憤.

I am experiencing this issue in the latests version of the development branch (commit bbe5d2c8096105a0906a676b521de48fb82d3704), when using the CredentialsPlugin with basic auth.

I worked around this by using the AccessTokenPlugin:

let authorizationPlugin = AccessTokenPlugin { Data("\(user):\(password)".utf8).base64EncodedString() }

Actually it's due to Alamofire I would try to investigate why is it.
But if you execute the following it will have the same behaviour.

Alamofire.request(url, method: .get)      
      .authenticate(user: "test", password: "test")

Edit:
I guess they explain it here in the issue https://github.com/Alamofire/Alamofire/issues/32

@hamdshah ah this is awesome, thanks for the findings!

another reference https://github.com/Alamofire/Alamofire/issues/1425
So the default behaviour of HTTP is following

image

So even in case of successful authentication it needs at least 2 request.
But with Alamofire in case of wrong credential it makes the request 1 more time, means total request are 3.

It happens due to the following

https://github.com/Alamofire/Alamofire/blob/master/Source/SessionDelegate.swift#L134

I'm not sure why .cancelAuthenticationChallenge is not used instead of rejectProtectionSpace.

@sunshinejr I guess we can close it now :)

@hamdshah 馃檶

Was this page helpful?
0 / 5 - 0 ratings