Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set a 'Token xxxxxxxxxx' for 'Authorization' in HTTP header in URLSession

The backend APIs I'm working with require a token to be sent with every request against the HTTP header key Authorization in this format - Token xxxxxxxxxx.

Right now, I'm doing the following.

var getRequest = URLRequest(url: url)
getRequest.addValue("Token xxxxxxxx", forHTTPHeaderField: "Authorization")

This works sometimes and some other times, the header field Authorization is stripped when the request is sent out. I checked this using Charles proxy.

Apple's documentation states the following.

An NSURLSession object is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers: Authorization, Connection, Host, WWW-Authenticate

As a solution to this, many suggest using the didReceiveAuthenticationChallenge delegate method for URLSession.

Here, you need to pass a URLSession.AuthChallengeDisposition instance to tell how you want to respond to the challenge and a URLCredential instance to pass the credentials to respond to the authenticate challenge.

I do not know how to and if I can create a URLCredential instance that will add Token xxxxxxxx for the header field Authorization.

Can somebody more knowledgeable please help me how to go about solving this?

PS - All code mentioned in this question is in Swift 3. This question asks something similar to what I have. But, the answers given there don't work for me. And, some of the questions asked under the questions regarding Apple not allowing headers for Authorization to be added have gone unanswered.

Edit:

Posting relevant code.

var getRequest = URLRequest(url: url)
getRequest.httpMethod = "GET"
getRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
getRequest.addValue("application/json", forHTTPHeaderField: "Accept")
let token = DataProvider.sharedInstance.token
getRequest.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
let getTask = URLSession.shared.dataTask(with: getRequest) { (data, response, error) in
        if let data = data {
            print("--------GET REQUEST RESPONSE START--------")
            print("CODE: \((response as? HTTPURLResponse)?.statusCode ?? 0)")
            print("Response Data:")
            print(String(data: data, encoding: .utf8) ?? "")
            print("--------GET REQUEST RESPONSE END--------")
        }
}
getTask.resume()

Here, I can confirm that the header field for 'Authorization' is getting added to the request's header dictionary.

But, when I check what request hits the server, the header field for 'Authorization' is missing. Any thoughts?

like image 787
Vishal V. Shekkar Avatar asked Jun 30 '17 09:06

Vishal V. Shekkar


1 Answers

I ran into this exact same issue and discovered that my lack of a trailing slash, /, was the problem.

The server was sending back a 301 Redirect response. URLSession automatically follows the redirect, but will also drop the Authorization header. This is likely due to Authorization's "special status". According to URLSessionConfiguration's documentation:

An URLSession object is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers:

  • Authorization
  • Connection
  • Host
  • WWW-Authenticate

If the Authorization header is required, implement urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:) from URLSessionTaskDelegate. That method is passed the new request on redirect, allowing you to add back the header.

e.g.

class APIClient: URLSessionTaskDelegate {
    let session: URLSession!
    initializeSession() {
        // Create a new session with APIClient as the delegate
        session = URLSession(configuration: URLSessionConfiguration.default,
                             delegate: self,
                             delegateQueue: nil)
    }

    // Perform the request
    func fetchRecords(handler: () => void) {
        var request = URLRequest(url: URL(string: "http://127.0.0.1:8000/api/employee/records")!)
        request.setValue(retrieveToken(), forHTTPHeaderField: "Authorization")
        session.dataTask(with: request, completionHandler: handler).resume()
    }

    // Implement URLSessionTaskDelegate's HTTP Redirection method
    func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
        // Create a mutable copy of the new request, add the header, call completionHandler
        var newRequest = request
        newRequest.addValue(retrieveToken(), forHTTPHeaderField: "Authorization")
        completionHandler(newRequest)
    }
}

IMPORTANT NOTE!

It's a bad idea to blindly trust a redirect. You should ensure that the URL you're being redirected to is the same domain as the original request. I've left that out of my code example for brevity.

like image 168
Kumaran Avatar answered Oct 21 '22 17:10

Kumaran