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?
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 .
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With