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

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?

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

Implement authentication challenge to handle Basic authentication like:
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
switch challenge.protectionSpace.authenticationMethod {
case NSURLAuthenticationMethodHTTPBasic:
performBasicAuthentication(challenge: challenge, completionHandler: completionHandler)
default:
completionHandler(.performDefaultHandling, nil)
}
}
func performBasicAuthentication(challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
completionHandler(.useCredential, credential)
}
Here is the reference link

Setting or adding a value directly like other suggestions doesn't work in my case. I managed to solve it by creating additional HTTP Headers URLSessionConfiguration. Here's the code:
var sessionConfig = URLSessionConfiguration.default
var authValue: String? = "Bearer \(token!)"
sessionConfig.httpAdditionalHeaders = ["Authorization": authValue]
var session = URLSession(configuration: sessionConfig, delegate: self as? URLSessionDelegate, delegateQueue: nil)
session.dataTask(with: request) { data, response, error in
guard error == nil else { return }
guard let data = data else { return }
if let str = String(data: data, encoding: .utf8) {
}
}.resume()

Related

URLSession dataTask long delay

When I'm requesting API from my code I got response after 4-6sec which is way too long. From Postman I'm getting response after 120ms. Is that something in my code goes wrong?
here is my code, I'm checking time between those two prints:
func makeUrlRequest<T: Codable>(_ request: URLRequest, resultHandler: #escaping (Result<T, RequestError>) -> Void) {
var req = request
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.addValue("application/json", forHTTPHeaderField: "Accept")
let config = URLSessionConfiguration.default
let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: .main)
print("Request: start at: \(Date())") //Request: start at: 2021-04-09 06:53:32 +0000
let urlTask = urlSession.dataTask(with: req) { data, response, error in
print("Request: finished at: \(Date())") //Request: finished at: 2021-04-09 06:53:36 +0000
DispatchQueue.main.async {
guard error == nil else {
resultHandler(.failure(.clientError))
return
}
guard let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode else {
resultHandler(.failure(.serverError))
return
}
guard let data = data else {
resultHandler(.failure(.noData))
return
}
guard let decodedData: T = self.decodedData(data) else {
resultHandler(.failure(.dataDecodingError))
return
}
resultHandler(.success(decodedData))
}
}
urlTask.resume()
}
Instead of
let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: .main)
print("Request: start at: \(Date())") //Request: start at: 2021-04-09 06:53:32 +0000
let urlTask = urlSession.dataTask(with: req) { data, response, error in...
You should use
let task = URLSession.shared.dataTask(with: req) { (data, response, error) in...
EDIT:
I see that you would like to use a delegate. In that case you shouldn't use a completion handler but instead add the delegate methods:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
Check out Apple's documentation for more info.

Upload video to Vimeo using URLSession

Im trying to upload a video to Vimeo , using their rest API.
First I make a POST, to create the video on the API, then I select the video from camera, and send a PATCH with some parameters.
The problem is, the video seems to upload with success, I get a HTML without any errors that seems a success, but in the site doesn't show the video, and the KB size is only 0.
Here's my PATCH method :
let request = NSMutableURLRequest(url: "https://api.vimeo.com/me/videos")
request.httpMethod = "PATCH"
request.cachePolicy = .reloadIgnoringLocalCacheData
request.httpBody = mediaData // Data()
request.setValue("Upload-Offset", forHTTPHeaderField: "0")
request.setValue("Content-Type", forHTTPHeaderField: "application/offset+octet-stream")
request.setValue("Authorization", forHTTPHeaderField: "Bearer 1233131312")
request.setValue("Tus-Resumable", forHTTPHeaderField: "1.0.0")
let newTask = session.uploadTask(withStreamedRequest: request)
newTask.resume()
and already tried with:
request.httpBodyStream = InputStream(data: mediaData)
or:
session.uploadTask(with: request, from: data)
Heres is the documentation from VIMEO :
https://developer.vimeo.com/api/upload/videos
Can anyone have an example or snippet that actually works?
[UPDATE]:
The class that make the request:
import Foundation
case get
case post
case put
case delete
case patch
var verbName: String {
switch self {
case .get:
return "GET"
case .post:
return "POST"
case .put:
return "PUT"
case .delete:
return "DELETE"
case .patch:
return "PATCH"
}
}
var containsBody: Bool {
return self == .post || self == .put || self == .patch
}
}
final class MediaUploader: URLSessionTaskDelegate, URLSessionDataDelegate {
private let dispatchGroup = DispatchGroup()
private let globalSSLEnabled: Bool = true
var outputStream: OutputStream? = nil
private var buffer:NSMutableData = NSMutableData()
convenience init?(domain: String) {
self.init(domain: domain)
}
func request(verb: HTTPVerb, action: String, parameters: JSONDictionary = [:], headers: [String : String] = [:], sslEnabled: Bool = true, media: MediaData, completion: #escaping (_ response: HTTPResponse) -> Void) {
let sslEnabled = self.globalSSLEnabled || sslEnabled
guard let request = buildRequest(verb: verb, action: action, parameters: parameters, media: media, sslEnabled: sslEnabled) else {
return
}
headers.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let session = buildURLSession(sslEnabled: sslEnabled)
conditionalLog(items: "REQUEST [\(verb.verbName)]")
conditionalLog(items: "URL: \(request.url?.absoluteString ?? "[invalid]")")
conditionalLog(items: "Headers: \(String(describing: request.allHTTPHeaderFields))")
conditionalLog(items: "Parameters: \(parameters)")
createSessionWithDataTask(session: session, request: request as URLRequest, mediaData: media, completion: completion)
}
func buildURLSession(sslEnabled: Bool) -> URLSession {
if !sslEnabled {
return URLSession(configuration: .default)
}
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}
func createSessionWithDataTask(session: URLSession, request: URLRequest, mediaData: MediaData,completion: #escaping (HTTPResponse) -> Void) {
let queue = DispatchQueue(label: "com.lassie.dispatchgroup", attributes: .concurrent, target: nil)
dispatchGroup.enter()
queue.async(group: dispatchGroup) {
self.dataTaskWorker(session: session, request: request as URLRequest, mediaData: mediaData, completion: completion)
self.dispatchGroup.leave()
}
}
func dataTaskWorker(session: URLSession, request: URLRequest, mediaData: MediaData, completion: #escaping (_ response: HTTPResponse) -> Void) {
guard let data = mediaData.data() else {
return
}
// let newTask = session.uploadTask(withStreamedRequest: request)
// let newTask = session.uploadTask(with: request, from: data)
//session.uploadTask(with: request, from: data)
let task = session.uploadTask(withStreamedRequest: request) { data, response, error in
self.conditionalLog(items: "RESPONSE: \(String(describing: response))")
if let eRROR = error {
self.conditionalLog(items: "ERROR : \(String(describing: eRROR))")
}
self.parseResponse(data: data, response: response, error: error, completion: completion)
session.finishTasksAndInvalidate()
}
newTask.resume()
}
func buildRequest(verb: HTTPVerb, action: String, parameters: JSONDictionary = [:], media: MediaData, sslEnabled: Bool) -> NSMutableURLRequest? {
let suffix = buildAction(verb: verb, action: action, parameters: parameters)
guard let fullUrl = suffix.isEmpty ? baseURL(sslEnabled: sslEnabled) : URL(string: suffix, relativeTo: baseURL(sslEnabled: sslEnabled)), let mediaData = media.data() else {
assert(false, "Invalid url/parameters")
return nil
}
let request = NSMutableURLRequest(url: fullUrl)
request.httpMethod = verb.verbName
request.cachePolicy = .reloadIgnoringLocalCacheData
request.httpBody = mediaData
// request.httpBodyStream = InputStream(data: mediaData)
return request
}
// MARK: URLSessionDataTask
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: #escaping (InputStream?) -> Void) {
self.closeStream()
var inStream: InputStream? = nil
var outStream: OutputStream? = nil
Stream.getBoundStreams(withBufferSize: 4096, inputStream: &inStream, outputStream: &outStream)
self.outputStream = outStream
completionHandler(inStream)
}
private func closeStream() {
if let stream = self.outputStream {
stream.close()
self.outputStream = nil
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
buffer.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let percentageSent = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
print("PERCENTAGE - \(percentageSent)")
}
private func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(.allow)
}
private func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let e = error {
print("ERROR")
} else {
self.conditionalLog(items: "RESPONSE DATA: \(String(data: buffer as Data, encoding: .utf8))")
}
}
}
You are using session.uploadTask(withStreamedRequest: request). The reason the file size is 0 on backend is you didn't close the stream, something like inputStream.finished = true. So you have to figure out when you want to close that stream.
Anyways, since you already have the data ready. i would say you can use uploadTask(with:from:completionHandler:), or even switching to uploadTask(with:fromFile:completionHandler:)

URLSession not working with URLCredential

I have an API I am trying to connect to and the server is Windows Authentication.
I am trying to use URLSession with URLCredential with the delegate methods
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void){
var disposition: URLSession.AuthChallengeDisposition = URLSession.AuthChallengeDisposition.performDefaultHandling
var credential:URLCredential?
print(challenge.protectionSpace.authenticationMethod)
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
if (credential != nil) {
disposition = URLSession.AuthChallengeDisposition.useCredential
}
else
{
disposition = URLSession.AuthChallengeDisposition.performDefaultHandling
}
}
else
{
disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
}
completionHandler(disposition, credential);
}
This code runs twice as after doing some printing is because there are two Authentication Methods:
NSURLAuthenticationMethodServerTrust and NSURLAuthenticationMethodNTLM when it runs through the NSURLAuthenticationMethodServerTrust everything is fine, but when it runs NSURLAuthenticationMethodNTLM I get an error on this line:
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
saying this:
fatal error: unexpectedly found nil while unwrapping an Optional value
but only when I change this condition from
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
to
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM {
What Am I doing wrong?
Here is the method I am using to try to connect to that API
func loginUser(_ username: String, password: String, completion: #escaping (_ result: Bool) -> Void)
{
//Create request URL as String
let requestString = String(format:"%#", webservice) as String
//Covert URL request string to URL
guard let url = URL(string: requestString) else {
print("Error: cannot create URL")
return
}
//Convert URL to URLRequest
let urlRequest = URLRequest(url: url)
print(urlRequest)
//Add the username and password to URLCredential
credentials = URLCredential(user:username, password:password, persistence: .forSession)
print(credentials)
//Setup the URLSessionConfiguration
let config = URLSessionConfiguration.default
//Setup the URLSession
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
//Prepare the task to get data.
let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
DispatchQueue.main.async(execute: {
print(error!)
if(error == nil)
{
completion(true)
}
else
{
completion(false)
}
})
})
//Run the task to get data.
task.resume()
}
In the Apple documentation for URLProtectionSpace.serverTrust, it says:
nil if the authentication method of the protection space is not server trust.
So, you are trying to unwrap an optional value of nil, which of course would cause a crash.
In your case, you could probably just replace the entire function with (untested):
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void){
var disposition: URLSession.AuthChallengeDisposition = URLSession.AuthChallengeDisposition.performDefaultHandling
print(challenge.protectionSpace.authenticationMethod)
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = URLSession.AuthChallengeDisposition.performDefaultHandling
}
else
{
disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
}
completionHandler(disposition, credential);
}

Swift - NSURLSession for Windows Authentication

I have this class here and inside the class is a method and I am trying to do an NSURLSession on an API that requires windows authentication username and password. I have followed the tutorial here https://gist.github.com/n8armstrong/5c5c828f1b82b0315e24
and came up with this:
let webservice = "https://api.com"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let urlSession = NSURLSession(configuration: config)
class WebService: NSObject {
func loginUser(username: String, password: String) -> Bool {
let userPasswordString = "username#domain.com:Password"
let userPasswordData = userPasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = userPasswordData!.base64EncodedStringWithOptions([])
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders = ["Authorization" : authString]
let requestString = NSString(format:"%#", webservice) as String
let url: NSURL! = NSURL(string: requestString)
let task = urlSession.dataTaskWithURL(url) {
(let data, let response, let error) in
if (response as? NSHTTPURLResponse) != nil {
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString)
}
}
task.resume()
return true
}
}
but when I run this I get a 401 error: 401 - Unauthorized: Access is denied due to invalid credentials.
I have confirmed the URL to the API is correct. Same with the username and password. What am I doing wrong?
I was able to fix this by doing the following:
var credential: NSURLCredential!
func loginUser(username: String, password: String) -> Bool {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
credential = NSURLCredential(user:username, password:password, persistence: .ForSession)
let requestString = NSString(format:"%#", webservice) as String
let url: NSURL! = NSURL(string: requestString)
let task = session.dataTaskWithURL(url, completionHandler: {
data, response, error in
dispatch_async(dispatch_get_main_queue(),
{
if(error == nil)
{
print("Yay!")
}
else
{
print("Naw!")
}
})
})
task.resume()
return true
}
and then adding NSURLSessionDelegate methods:
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if challenge.previousFailureCount > 0
{
completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil)
}
else
{
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust:challenge.protectionSpace.serverTrust!))
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential)
}

How to use NSURLSession to determine if a redirected link is broken?

I'm trying to use NSURLSession to determine if a link is broken after passing in an array of URLs. The problem with the below code is that myURL in the line else { print("no data back from getDataFromSErverWithSuccess and this is the problem: \(myURL)")} returns nil. How can I get the value of the URL that is broken?
class MySession: NSObject, NSURLSessionDelegate {
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest!) -> Void) {
if let checkedURL = request.URL {
if UIApplication.sharedApplication().canOpenURL(checkedURL) {
print("\(checkedURL) works")
} else {
print("\(checkedURL) IS BROKE!")
}
}
}
// fetch data from URL with NSURLSession
class func getDataFromServerWithSuccess(myURL: String, noRedirect: Bool, success: (response: String!) -> Void) {
var myDelegate: MySession? = nil
if noRedirect {
myDelegate = MySession()
}
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: myDelegate, delegateQueue: nil)
let loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if let checkedData = data {
success(response: NSString(data: checkedData, encoding: NSASCIIStringEncoding) as! String)
} else { print("no data back from getDataFromSErverWithSuccess and this is the problem: \(myURL)")}
}
loadDataTask.resume()
}
EDIT: this is where I'm calling getDataFromServerWithSuccess can I get the url by adding an error block here?
MySession.getDataFromServerWithSuccess(urlAsString, noRedirect: false, success:
{ (response: String!) -> Void in
})
You can obtain the redirected request with property currentRequest from the session task.
Source: Apple Documentation

Resources