Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 5 URLRequest authorization header: reserved, how to set?

In the documentation for Swift's URLRequest in Foundation, it says that the standard method of setting header values for a URLRequest shouldn't be used for reserved HTTP headers.

Following the link to the list of reserved HTTP headers a little bit deeper in the docs, it says that it may ignore attempts to set those headers.

But it also says that Authorization is a reserved HTTP header.

This can't be right, can it? A large percentage of the APIs in the universe require you to pass authentication tokens in a header of the form Authorization: Bearer {token}

So if Swift doesn't let you set the Authorization header, how does one access one of those APIs?

like image 766
Paul Gowder Avatar asked May 17 '19 22:05

Paul Gowder


People also ask

How do I change the Authorization header in Swift?

To add the Authorization header to the request, we invoke addValue(_:forHTTPHeaderField:) on the URLRequest object, passing in the encoded credentials and the name of the header, Authorization .

How do I pass credentials in Authorization header?

It is a simple authentication scheme built into the HTTP protocol. The client sends HTTP requests with the Authorization header that contains the word Basic, followed by a space and a base64-encoded(non-encrypted) string username: password. For example, to authorize as username / Pa$$w0rd the client would send.

How do I send Authorization header bearer?

To send a request with the Bearer Token authorization header, you need to make an HTTP request and provide your Bearer Token with the "Authorization: Bearer {token}" header. A Bearer Token is a cryptic string typically generated by the server in response to a login request.


1 Answers

Following the documentation as you all mentioned, I've ended up for now to the following:

class ApiManager: NSObject {

    var credential: URLCredential?
    
    func token(withCredential credential: URLCredential?) {
        guard let url = URL(string: "\(K.API)/token") else {
            print("error URL: \(K.API)/token")
            return
        }

        self.credential = credential
        
        let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "accept")
        request.setValue("application/json", forHTTPHeaderField: "content-type")
        
        let task = session.dataTask(with: request) { (data, response, error) in
            self.credential = nil
            
            if error != nil {
                print("URLSession error: \(error!.localizedDescription)")
                return
            }
            
            guard let safeHttpResponse = response as? HTTPURLResponse else {
                print("HTTPURLResponse error: \(error!.localizedDescription)")
                return
            }
            
            if safeHttpResponse.statusCode == 200,
               let safeData = data,
               let dataString = String(data: safeData, encoding: .utf8) {
                print("safeData: \(dataString)")
            } else {
                print("error: \(safeHttpResponse.statusCode)")
            }
        }
        
        task.resume()
    }
    
}

Here, token is a method as an example to authenticate a user.

I pass something like that from the UI to this method

URLCredential(user: usernameTextField.text, password: passwordTextField.text, persistence: .forSession)

Then the most important, is the URLSessionTaskDelegate

extension ApiManager: URLSessionTaskDelegate {
    // From https://developer.apple.com/forums/thread/68809
    // We should use session delegate as setting Authorization Header won't always work
    
    func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // This method is called mainly with HTTPS url
        
        let protectionSpace = challenge.protectionSpace
        let authMethod = protectionSpace.authenticationMethod
        
        guard authMethod == NSURLAuthenticationMethodServerTrust, protectionSpace.host.contains(K.API.host) else {
            completionHandler(.performDefaultHandling, nil)
            return
        }
        
        guard let safeServerTrust = protectionSpace.serverTrust else {
            completionHandler(.performDefaultHandling, nil)
            return
        }
        
        DispatchQueue.global().async {
            SecTrustEvaluateAsyncWithError(safeServerTrust, DispatchQueue.global()) { (trust, result, error) in
                if result {
                    completionHandler(.useCredential, URLCredential(trust: trust))
                } else {
                    print("Trust failed: \(error!.localizedDescription)")
                    completionHandler(.performDefaultHandling, nil)
                }
            }
        }
    }
    
    func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // This method is called for authentication
        
        let protectionSpace = challenge.protectionSpace
        let authMethod = protectionSpace.authenticationMethod
        
        switch (authMethod, protectionSpace.host) {
            case (NSURLAuthenticationMethodHTTPBasic, K.API.host):
                self.basicAuth(didReceive: challenge, completionHandler: completionHandler)
            // we could add other authentication e.g Digest
            default:
                completionHandler(.performDefaultHandling, nil)
        }
    }
    
    private func basicAuth(
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        
        if challenge.previousFailureCount < 3 {
            completionHandler(.useCredential, self.credential)
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
    
}

And I call everything like this:

let apiManager = ApiManager()
let credential = URLCredential(user: email, password: password, persistence: .forSession)
apiManager.token(withCredential: credential)

I have to handle the response with a completionHandler for example but the request is authenticated and works

like image 105
Sovanna Avatar answered Sep 20 '22 13:09

Sovanna