title: HTTP Basic Auth with NSURLSession
body: <p>I'm trying to implement HTTP Basic Auth with <code>NSURLSession</code>, but I run into several issues. Please read the entire question before responding, I doubt this is a duplicate of an other question.</p>
  
  <p>According to the tests I've run, the behavior of <code>NSURLSession</code> is the following :</p>
  
  <ul>
  <li>The first request is always made without the <code>Authorization</code> header.</li>
  <li>If the first request fails with a <code>401 Unauthorized</code> response and a <code>WWW-Authenticate Basic realm=...</code> header, it is automatically retried.</li>
  <li>Before retrying the request, the session will attempt to obtain credentials by looking into the <code>NSURLCredentialStorage</code> of the session configuration or by calling the <code>URLSession:task:didReceiveChallenge:completionHandler:</code> delegate method (or both).</li>
  <li>If credentials could be obtained the request is retried with the proper  <code>Authorization</code> header. If not it is retried without the header (which is weird because in this case, this is exactly the same request).</li>
  <li>If the second request succeeds, the task is transparently reported as successful and you're not even notified that the request was attempted twice. If not, the failure of the second request is reported (but not the first).</li>
  </ul>
  
  <p>The problem I have with this behavior is that I am uploading large files to my server through multipart requests, so when the request is attempted twice, the entire <code>POST</code> body is sent twice which is a terrible overhead.</p>
  
  <p>I have tried to manually add the <code>Authorization</code> header to the <code>httpAdditionalHeaders</code> of the session configuration, but it works only if the property is set <em>before</em> the session is created. Attempting to modify <code>session.configuration.httpAdditionalHeaders</code> afterwards doesn't work. Also the documentation clearly says that the <code>Authorization</code> header should not be set manually.</p>
  
  <hr>
  
  <p><strong>So my question is</strong>: If I need to start the session before I obtain the credentials and If I want to be sure that requests are always made with the proper  <code>Authorization</code> header the first time, how do I do ?</p>
  
  <hr>
  
  <p>Here is a code sample that I've used for my tests. You can reproduce all the behaviors I've described above with it.</p>
  
  <p>Note that in order to be able to see the double requests you wil need to either use your own http server and log the requests or connect through a proxy that logs all requests (I've used <a href="https://www.charlesproxy.com" rel="nofollow noreferrer">Charles Proxy</a> for this)</p>
  
  <pre><code>class URLSessionTest: NSObject, URLSessionDelegate
  {
      static let shared = URLSessionTest()
  
      func start()
      {
          let requestURL = URL(string: "https://httpbin.org/basic-auth/username/password")!
          let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
          let protectionSpace = URLProtectionSpace(host: "httpbin.org", port: 443, protocol: NSURLProtectionSpaceHTTPS, realm: "Fake Realm", authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
  
          let useHTTPHeader = false
          let useCredentials = true
          let useCustomCredentialsStorage = false
          let useDelegateMethods = true
  
          let sessionConfiguration = URLSessionConfiguration.default
  
          if (useHTTPHeader) {
              let authData = "\(credential.user!):\(credential.password!)".data(using: .utf8)!
              let authValue = "Basic " + authData.base64EncodedString()
              sessionConfiguration.httpAdditionalHeaders = ["Authorization": authValue]
          }
          if (useCredentials) {
              if (useCustomCredentialsStorage) {
                  let urlCredentialStorage = URLCredentialStorage()
                  urlCredentialStorage.set(credential, for: protectionSpace)
                  sessionConfiguration.urlCredentialStorage = urlCredentialStorage
              } else {
                  sessionConfiguration.urlCredentialStorage?.set(credential, for: protectionSpace)
              }
          }
  
          let delegate = useDelegateMethods ? self : nil
          let session = URLSession(configuration: sessionConfiguration, delegate: delegate, delegateQueue: nil)
  
          self.makeBasicAuthTest(url: requestURL, session: session) {
              self.makeBasicAuthTest(url: requestURL, session: session) {
                  DispatchQueue.main.asyncAfter(deadline: .now() + 61.0) {
                      self.makeBasicAuthTest(url: requestURL, session: session) {}
                  }
              }
          }
      }
  
      func makeBasicAuthTest(url: URL, session: URLSession, completion: @escaping () -&gt; Void)
      {
          let task = session.dataTask(with: url) { (data, response, error) in
              if let response = response {
                  print("response : \(response)")
              }
              if let data = data {
                  if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) {
                      print("json : \(json)")
                  } else if data.count &gt; 0, let string = String(data: data, encoding: .utf8) {
                      print("string : \(string)")
                  } else {
                      print("data : \(data)")
                  }
              }
              if let error = error {
                  print("error : \(error)")
              }
              print()
              DispatchQueue.main.async(execute: completion)
          }
          task.resume()
      }
  
      @objc(URLSession:didReceiveChallenge:completionHandler:)
      func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -&gt; Swift.Void)
      {
          print("Session authenticationMethod: \(challenge.protectionSpace.authenticationMethod)")
          if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
              let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
              completionHandler(.useCredential, credential)
          } else {
              completionHandler(.performDefaultHandling, nil)
          }
      }
  
      @objc(URLSession:task:didReceiveChallenge:completionHandler:)
      func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -&gt; Swift.Void)
      {
          print("Task authenticationMethod: \(challenge.protectionSpace.authenticationMethod)")
          if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
              let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
              completionHandler(.useCredential, credential)
          } else {
              completionHandler(.performDefaultHandling, nil)
          }
      }
  }
  </code></pre>
  
  <p><em>Note 1</em>: When making multiple requests in a row to the same endpoint, the behavior I've described above concerns only the first request. Subsequent requests are tried with the proper <code>Authorization</code> header the first time. However, if you wait some time (about 1 minute), the session will return to the default behavior (first request tried twice).</p>
  
  <p><em>Note 2</em>: This is not directly related, but using a custom <code>NSURLCredentialStorage</code> for the <code>urlCredentialStorage</code> of the session configuration doesn't seem to work. Only using the default value (which is the shared <code>NSURLCredentialStorage</code> according to the documentation) works.</p>
  
  <p><em>Note 3</em>: I've tried using <code>Alamofire</code>, but since it's based on <code>NSURLSession</code>, it behaves in the exact same way.</p>