URLSession doesn't pass 'Authorization' key in header swift 4 - ios

I am trying to pass authorization key in header of a URLRequest. But at the server end the key is not received. The same API when called from postman working fine. Any other key in the header is working fine, even authorizations key is visible at server end.
Here is my code:
let headers = [
"authorization": "token abcd"
]
var request = URLRequest.init(url: NSURL(string:
"http://127.0.0.1:7000/api/channels?filter=contributed")! as URL)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = headers
let session = URLSession.init(configuration: config)
let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error ?? "")
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse ?? "")
}
})
As you can see, I tried to set the token in both session config and request but none is working.

This seems to be working:
// Set the security header
private var credentials: String {
return "\(participantId):\(password)"
}
private var basicAuthHeader: String {
return "Basic \(credentials)"
}
func getSettings(participantId: Int, password: String) -> Bool {
self.participantId = participantId
self.password = password
let path = "/settings/\(participantId)"
guard let url = URL(string: "\(BASE_URL)\(path)") else {
Log.e("Invalid URL string, could not convert to URL")
return false
}
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue(basicAuthHeader, forHTTPHeaderField: "Authorization")
urlRequest.setValue(APP_FILE_NAME, forHTTPHeaderField: "User-Agent")
// This is a synchronous wrapper extension around URLSession.dataTask()
let (data, response, error) = URLSession.shared.synchronousDataTask(with: urlRequest)
// Process the result...
}
Note: code written by my coworker. Thanks John!

Looks like the problem is that you are modifying Authorization header using httpAdditionalHeaders which is something you should not do.
From the Doc
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,
Proxy-Authenticate,
Proxy-Authorization,
WWW-Authenticate
Removing the line config.httpAdditionalHeaders = headers
should fix the issue.

If you want token to be hardcoded, I guess it has to be like this:
urlRequest.httpMethod = "GET"
urlRequest.setValue("Token <Your Token>", forHTTPHeaderField: "Authorization")

I found the same thing: setting the header field Authorization just didn't do the trick.
Here's the solution I settled on (which works well):
I added the URLSessionDelegate protocol to my current class. This unfortunately means inheriting from NSObject.
Then, when defining my URLSession, I set its delegate to 'self'.
Finally, I provide an authentication challenge handler.
In code, this all looks like:
public class SomeHTTPTask: NSObject, URLSessionDelegate {
public init() {
... initialize variables ...
super.init()
... now you are free to call methods on self ...
}
public func httpTask(withURL url: URL) {
let request = URLRequest(url: url)
... set up request ...
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: request) {data, response, error in
... now you have a result ...
}
}
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let user = Credentials.sharedInstance.userId, let password = Credentials.sharedInstance.password else {
completionHandler(.performDefaultHandling, nil)
return
}
let userCredential = URLCredential(user: user,
password: password,
persistence: .permanent)
completionHandler(.useCredential, userCredential)
}
}
Hopefully, the bits and pieces are self-explanatory. It's just an authentication challenge handler that provides credentials, if it can. The underlying URLSession will deal with the details, wither it's an NTLM or Basic auth, those sorts of things.
In the end, this seems a solid solution. At least, it worked for me.
Here's a nice reference document from Apple if you like reading that kind of thing.

Related

401 Authorization Error when using URLSession

I am trying to retrieve JSON from my app's server which needs a user/password authentication. Does anyone know why I am not being allowed entry into the server? I tried including an authorization header with the credentials needed but still get this error.
func retrieveJSON(){
let login = "login#mail.com"
let password = "password"
let url = NSURL(string: "http://server/admin/data.json")
let request = NSMutableURLRequest(url: url! as URL)
let config = URLSessionConfiguration.default
let userPasswordString = "\(login):\(password)"
let userPasswordData = userPasswordString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData!.base64EncodedString()
let authString = "Basic \(base64EncodedCredential)"
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
debugPrint(response)
}
task.resume()
}
Response message with 401 error
Server authorization needed
Your web site does not appear to be using “basic” auth. It would appear to be using some different authentication scheme, which we cannot determine from an image of the HTML login page.
If it were using “Basic” auth (which it likely is not in your particular case), you could simplify the code a bit, removing NSURL, NSMutableURLRequest, the casts, etc. Also, if you’re going to create a URLSession, you will want to finishTasksAndInvalidate:
func retrieveJSON() {
let login = "login#mail.com"
let password = "password"
let url = URL(string: "http://server/admin/data.json")!
let request = URLRequest(url: url) // use `var` if you really need it to be mutable
let config = URLSessionConfiguration.default
let base64EncodedCredential = (login + ":" + password)
.data(using: .utf8)!
.base64EncodedString()
config.httpAdditionalHeaders = ["Authorization": "Basic " + base64EncodedCredential]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { data, response, error in
debugPrint(response ?? "No response")
}
task.resume()
session.finishTasksAndInvalidate()
}
Alternatively, rather than building the Authorization header yourself, you can call CFHTTPMessageAddAuthentication to add authentication headers to a request. And, as an aside, adding the authentication to the request itself, you don’t have to create your own URLSession, but can use the shared instance.
func retrieveJSON() {
let login = "login#mail.com"
let password = "password"
let url = URL(string: "http://server/admin/data.json")!
var request = URLRequest(url: url)
request.updateBasicAuth(for: login, password: password)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
debugPrint(response ?? "No response")
}
task.resume()
}
Where
extension URLRequest {
/// Update request for HTTP authentication
///
/// - parameter username: The username
/// - parameter password: The password
/// - parameter authentication: The type of authentication to be applied
mutating func updateBasicAuth(for username: String, password: String, authentication: String = kCFHTTPAuthenticationSchemeBasic as String) {
let message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, httpMethod! as CFString, url! as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
if !CFHTTPMessageAddAuthentication(message, nil, username as CFString, password as CFString, authentication as CFString?, false) {
print("authentication not added")
}
if let authorizationString = CFHTTPMessageCopyHeaderFieldValue(message, "Authorization" as CFString)?.takeRetainedValue() {
setValue(authorizationString as String, forHTTPHeaderField: "Authorization")
} else {
print("didn't find authentication header")
}
}
}
But refinements to the “Basic” authentication are somewhat academic if your server is using a different authentication scheme.

Google calendar API delete / update recurring event returns 412 on URLSession

I am trying to delete a parent recurring event (so the parent and all the instances will be deleted).
When using URLSession with manually constructed URLRequest the request returns 412 error code. I do not provide If-Match header and there have been no changes whatsoever to the event from the creation to its deletion.
Executing the request with Postman or Paw the request succeeds with status code 204 which is the desired result.
I construct the request as such:
func delete(eventWithId eventId: String, token: String) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
guard var URL = URL(string: "https://www.googleapis.com/calendar/v3/calendars/primary/events/\(eventId)") else {return}
var request = URLRequest(url: URL)
request.httpMethod = "DELETE"
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if (error == nil) {
let statusCode = (response as! HTTPURLResponse).statusCode // <--- returning status code 412
}
else {
print("URL Session Task Failed: %#", error!.localizedDescription);
}
})
task.resume()
session.finishTasksAndInvalidate()
}
The response body is:
{
"error":{
"errors":[
{
"domain":"global",
"reason":"conditionNotMet",
"message":"Precondition Failed",
"locationType":"header",
"location":"If-Match"
}
],
"code":412,
"message":"Precondition Failed"
}
}
Weird thing. I do the same DELETE request using Postman and PAW and it succeeds, returning status code 204. Also my delete request seems to be working fine in single events or single occurrences of the recurring event.
Anyone with experience on the matter?
So what was actually happening, was that there was a "If-None-Match" header added that wouldn't show up when I was debugging the request object. I had to audit the request using (Charles) proxy to see it.
Overriding this header field seems to have resolved this issue.

Missing headers in uploadTask allHeaderFields. Doesn't include custom headers from Access-Control-Expose-Headers

My server is using CORS. When a user logs in successfully, the response includes the headers: access-token, uid, client
The server response headers include: Access-Control-Expose-Headers:access-token, uid, client
However, when I get a successful response from an uploadTask, and access allHeaderFields these keys/values are missing.
What do I need to do to access these headers?
Thanks!
EDIT Adding client code that works just fine now:
func postReq(url: URL) -> URLRequest{
var request: URLRequest = URLRequest.init(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "content-type")
return request
}
func login(){
let url:URL = baseEndpoint.appendingPathComponent(Endpoints.login.rawValue)
let request: URLRequest = postReq(url: url)
let body: [String : String] = ["email" : "test#test.com", "password": "loremipsum"]
let bodyData:Data = try! JSONSerialization.data(withJSONObject: body)
uploadTask = defaultSession.uploadTask(with: request, from: bodyData, completionHandler: { (responseData, response, error) in
if(error == nil){
let headers = (response as! HTTPURLResponse).allHeaderFields
}
})
uploadTask?.resume()
}
ANNNND Fixed my problem. There wasn't an issue, I was just missing the correct content type. Facepalm.

what am I doing wrong Swift 3 http request and response

I'm having big problems in a project I'm currently working with.
I've been reading about URLSession various places but all of them seem to be outdated and refers to NSURLSession I thought that they would be fairly similar and they probably are but for a newbie like me I'm lost. what I do is not working and I do not like solutions I find because they all do their work in a controller..
http://swiftdeveloperblog.com/image-upload-with-progress-bar-example-in-swift/
this one for instance. I'm using the PHP script but wanted to make a networking layer I could invoke and use at will. but I'm lacking a good resource from where I could learn about how to use this api.
every place I find is similar to the link above or older. the few newer seem to also follow the pattern without really explaining how to use this api.
at the same time I'm new to the delegate pattern in fact I only know that it is something that is heavily used in this Api but I have no IDEA how or why.
Basically I need help finding my way to solve this problem here:
I've tried to do something like this:
public class NetworkPostRequestor:NSObject,NetworkPostRequestingProtocol,URLSessionTaskDelegate,URLSessionDataDelegate
{
public var _response:HTTPURLResponse
public override init()
{
_response = HTTPURLResponse()
}
public func post(data: Data, url: URL)
{
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration,delegate: self, delegateQueue: OperationQueue.main)
let task = session.uploadTask(with: request, from: data)
task.resume()
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void)
{
_response = response as! HTTPURLResponse
}
}
however I never even hit the PHPserver. the server when hit will say something like this in the terminal:
[Tue Mar 7 11:43:20 2017] 192.168.250.100:64265 [200]: /
[Tue Mar 7 11:43:20 2017] 192.168.250.100:64266 [404]: /favicon.ico - No such file or directory
Well that is when I hit it with my browser and there is no image with it. but alt least I know that it will write something with the terminal if it hits it. Nothing happens And without a resource to teach me this api I'm afraid I will never learn how to fix this or even if I'm doing something completely wrong.
I'm using Swift 3 and Xcode 8.2.1
Edit:
I've added this method to the class and found that I hit it every single time.
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
{
_error = error.debugDescription
}
the debug description have this string "some"
I never used this exact procedure with tasks but rather use the methods with callback. I am not sure if in the background there should be much of a difference though.
So to generate the session (seems pretty close to your):
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
Then I generate the request which stupidly enough needs an URL in the constructor:
var request = URLRequest(url: URL(string: "www.nil.com")!) // can't initialize without url
request.url = nil
Adding url with query parameters (you can just set the URL in your case, I have a tool to handle a few cases):
fileprivate func injectQueryParameters(request: inout URLRequest) {
if let query = queryParameters.urlEncodedString {
let toReturn = endpoint.url + "?" + query
if let url = URL(string: toReturn) {
request.url = url
} else {
print("Cannot prepare url: \(toReturn)")
}
} else {
let toReturn = endpoint.url
if let url = URL(string: toReturn) {
request.url = url
} else {
print("Cannot prepare url: \(toReturn)")
}
}
}
Then the form parameters. We mostly use JSON but anything goes here:
fileprivate func injectFormParameters( request: inout URLRequest) {
if let data = rawFormData {
request.httpBody = data
} else if let data = formParameters.urlEncodedString?.data(using: .utf8) {
request.httpBody = data
}
}
And the headers:
fileprivate func injectHeaders(request: inout URLRequest) {
headers._parameters.forEach { (key, value) in
if let stringValue = value as? String {
request.setValue(stringValue, forHTTPHeaderField: key)
}
}
}
So in the end the whole call looks something like:
class func performRequest(request: URLRequest, callback: (([String: Any]?, NSError?) -> Void)?) {
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request) { data, response, error in
// Response is sent here
if let data = data {
callback?((try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as [String: Any]?, error)
} else {
callback?(nil, error)
}
}
task.resume()
}
I hope this puts you on the right track. In general you do have a few open source libraries you might be interested in. Alamofire is probably still used in most cases.

Making HTTP Request with header in Swift

I am trying to make an HTTP request to the Imgur API. I am trying to retrieve all images associated with the tag "cats." The url, according to the Imgur API is: https://api.imgur.com/3/gallery/t/cats
the Imgur API states the following about the authorization needed to make get requests:
For public read-only and anonymous resources, such as getting image
info, looking up user comments, etc. all you need to do is send an
authorization header with your client_id in your requests. This also
works if you'd like to upload images anonymously (without the image
being tied to an account), or if you'd like to create an anonymous
album. This lets us know which application is accessing the API.
Authorization: Client-ID YOUR_CLIENT_ID
I've looked at the following questions and tried things suggested there, but none of them have helped.
JSON NSURLRequest with credentials
Swift GET request with parameters
How to make a Http get and set httpHeader in Swift?
My current code is this:
let string = "https://api.imgur.com/3/gallery/t/cats"
let url = NSURL(string: string)
let request = NSMutableURLRequest(URL: url!)
request.setValue("clientIDhere", forHTTPHeaderField: "Authorization")
//request.addValue("clientIDhere", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
let session = NSURLSession.sharedSession()
let tache = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if let antwort = response as? NSHTTPURLResponse {
let code = antwort.statusCode
print(code)
}
}
tache.resume()
But I continually get a status code of 403, meaning authorization is required. What am I doing wrong?
I think you need to prepend Client-ID string to your actual client ID as for the header value:
request.setValue("Client-ID <your_client_id>", forHTTPHeaderField: "Authorization")
Updated for swift 4 :
func fetchPhotoRequest(YOUR_CLIENT_ID: String) {
let string = "https://photoslibrary.googleapis.com/v1/albums"
let url = NSURL(string: string)
let request = NSMutableURLRequest(url: url! as URL)
request.setValue(YOUR_CLIENT_ID, forHTTPHeaderField: "Authorization") //**
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let mData = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
if let res = response as? HTTPURLResponse {
print("res: \(String(describing: res))")
print("Response: \(String(describing: response))")
}else{
print("Error: \(String(describing: error))")
}
}
mData.resume()
}

Resources