401 Authorization Error when using URLSession - ios

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.

Related

JWT Authentication with Alamofire download function

I'm trying to download a zip file and save it from server using JWT token authentication thanks to Alamofire. The download works well without token authentication, the file is saved with success. When I activate the server-side authentication (using Passport.js with NodeJS), I always received 401. I attach the token to the header with the sessionManager adapter function. Others request (post, get using sessionManager.request(..) ) works well with this authentication mechanism.
Question is : Can we modify the header of Alamofire download function ? If yes how ?
Any advices appreciated
func getZip(){
let sessionManager = Alamofire.SessionManager.default
let authHandler = JWTAccessTokenAdapter(accessToken: Auth.getAccessToken())
sessionManager.retrier = authHandler
sessionManager.adapter = authHandler
let downloadUrl: String = Auth.getApiEndpoint() + "get_zip"
let destinationPath: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
let fileURL = documentsURL.appendingPathComponent("myZip.zip")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
sessionManager.download(downloadUrl, method: .get, encoding: URLEncoding.httpBody, to: destinationPath)
.downloadProgress { progress in
print(">> Zip Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
switch response.result{
case .success:
if response.destinationURL != nil, let filePath = response.destinationURL?.absoluteString {
print("success & filepath : \(filePath)")
completionHandler(filePath, true)
}
break
case .failure:
print("faillure")
completionHandler("", false)
break
}
}
}
}
JWT Adapter :
class JWTAccessTokenAdapter: RequestAdapter {
typealias JWT = String
private var accessToken: JWT
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(Auth.getApiEndpoint()) {
/// Set the Authorization header value using the access token.
urlRequest.setValue(accessToken, forHTTPHeaderField: "Authorization")
}
return urlRequest
}
}
Output :
response: SUCCESS: 12 bytes // (Unauthorized) -> Corrupted zip file
Without a validation step in your request chain, all responses will be considered successful. So check your response code (or just add .validate() before responseData) and see if your request is actually successful. Also, you may want to double check your parameter encoding, though you don't seem to be sending any parameters.

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

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.

iOS Yelp OAuth Token Retrieval with URLRequest returning "client_id or client_secret parameters not found

I am attempting to retrieve an OAuth token to use Yelp's Fusion API from an iOS client using the native URL and URLRequest classes, but it is giving me this error in the "tokenInfo" variable:
client_id or client_secret parameters not found. Make sure to provide
client_id and client_secret in the body with the
application/x-www-form-urlencoded content-type
Here is my code:
func getToken(){
var yelpTokenEndpoint = "https://api.yelp.com/oauth2/token"
var tokenURL = URL(string: yelpTokenEndpoint)
let requestJSON: [String:String] = ["client_id":"Not showing actual client id", "client_secret":"Not Gonna Show My Actual Client Secret either","grant_type":"client_credentials"]
let requestData = try? JSONSerialization.data(withJSONObject: requestJSON)
print(try? JSONSerialization.jsonObject(with: requestData!, options: []))
var tokenURLRequest = URLRequest(url: tokenURL!)
tokenURLRequest.httpMethod = "POST"
tokenURLRequest.httpBody = requestData!
tokenURLRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "content-type")
let tokenSession = URLSession.shared
let tokenTask = tokenSession.dataTask(with: tokenURLRequest) { (data, response, error) in
if error != nil {
print("error getting your access token")
print(error!.localizedDescription)
}
if let data = data{
do{
if let tokenInfo = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any]{
let token: String = tokenInfo["access_token"] as! String
print(token)
}
} catch {
print("Error converting to JSON")
}
}
}
tokenTask.resume()
}
And yes, I am certain that I put the right client credentials in. Any help would be much appreciated, thanks!
Try this ....
let clientId = "client_id"
let clientSecret = "client_secret"
let tokenURL = "https://api.yelp.com/oauth2/token"
let grantType = "client_credentials"
let url = NSURL(string: tokenURL as String );
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSMutableURLRequest(URL: NSURL(string: tokenURL)!)
request.HTTPMethod = "POST";
request.HTTPShouldHandleCookies = true
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let postString = "client_id=" + clientId + "&client_secret=" + clientSecret + "&grant_type=" + grantType
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if let data = data {
let response = NSString(data: data, encoding: NSUTF8StringEncoding)
print(response)
}
}
task.resume()

How to make a NSURLSesssion GET request with cookies

I'm using the Pinterest SDK to download a Pinterest Pin's link, (sample link that I get back from the server: https://www.pinterest.com/r/pin/186195765822871832/4801566892554728205/77314e40aeb26c0dc412e9cfa82f8dccc401fdb2b9806a3fe17ba8bafdb50510).
About 5 days ago I started getting 404 errors in my NSURLSesssion when trying to access similar links that I'd pulled down from Pinterest.
A friend said that he believes Pinterest must now require cookies to access that link.
How can I configure my session so that I can use cookies and get a 200 response back from Pinterest?
UPDATED CODE:
import UIKit
import PlaygroundSupport
var url = URL(string: "https://www.pinterest.com/r/pin/186195765822871832/4801566892554728205/77314e40aeb26c0dc412e9cfa82f8dccc401fdb2b9806a3fe17ba8bafdb50510")
var getSourceURLFromPinterest: URLSessionDataTask? = nil
let sessionConfig: URLSessionConfiguration = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 30.0
sessionConfig.timeoutIntervalForResource = 30.0
let cookieJar = HTTPCookieStorage.shared
let cookieHeaderField = ["Set-Cookie": "key=value, key2=value2"]
let cookies = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField, for: url!)
HTTPCookieStorage.shared.setCookies(cookies, for: url, mainDocumentURL: url)
let session = URLSession(configuration: sessionConfig)
getSourceURLFromPinterest = session.dataTask(with: url! as URL) { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if error != nil {
print("error is \(error)")
}
if response == nil {
print("no response")
} else if let _ = data {
//Config Request
let request = NSMutableURLRequest(
url: (response?.url)!,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
request.httpMethod = "HEAD"
var statusCode = Int()
let session = URLSession.shared
let checkURLForResponse = session.dataTask(with: request as URLRequest) {urlData, myResponse, responseError in
if let httpResponse = myResponse as? HTTPURLResponse {
statusCode = httpResponse.statusCode
switch statusCode {
case _ where statusCode < 500 && statusCode > 299 && statusCode != 405: //whitelisted 405 to exclude Amazon.com false errors
print("status code \(statusCode) for \(url)")
default:
break;
}
} else { print("***NO httpResponse for \(url)") }
}
checkURLForResponse.resume()
}
}
getSourceURLFromPinterest!.resume()
PlaygroundPage.current.needsIndefiniteExecution = true
The other answers may work generally, but specifically for me this is how I coded the request in order to get a response from Pinterest's server. Note that specifically what I am doing I think is related to a possible bug in Pinterest's server, see: https://github.com/pinterest/ios-pdk/issues/124
I commented out my personal Pinterest session ID
var cookieSession = String()
var cookieCSRFToken = String()
var myWebServiceUrl = URL(string: "https://www.pinterest.com/r/pin/186195765821344905/4801566892554728205/a9bb098fcbd6b73c4f38a127caca17491dafc57135e9bbf6a0fdd61eab4ba885")
let requestOne = URLRequest(url: myWebServiceUrl!)
let sessionOne = URLSession.shared
let taskOne = sessionOne.dataTask(with: requestOne, completionHandler: { (data, response, error) in
if let error = error {
print("ERROR: \(error)")
}
else {
print("RESPONSE: \(response)")
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("DATA: " + dataString)
} // end: if
var cookies:[HTTPCookie] = HTTPCookieStorage.shared.cookies! as [HTTPCookie]
print("Cookies Count = \(cookies.count)")
for cookie:HTTPCookie in cookies as [HTTPCookie] {
// Get the _pinterest_sess ID
if cookie.name as String == "_pinterest_sess" {
//var cookieValue : String = "CookieName=" + cookie.value as String
cookieSession = cookie.value as String
print(cookieSession)
}
// Get the csrftoken
if cookie.name as String == "csrftoken" {
cookieCSRFToken = cookie.value
print(cookieCSRFToken)
}
} // end: for
} // end: if
})
taskOne.resume()
var requestTwo = URLRequest(url: myWebServiceUrl!)
cookieSession = "XXXXXXXX"
cookieCSRFToken = "JHDylCCKKNToE4VXgofq1ad3hg06uKKl"
var cookieRequest = "_auth=1; _b=\"AR4XTkMmqo9JKradOZyuMoSWcMdsBMuBHHIM21wj2RPInwdkbl2yuy56yQR4iqxJ+N4=\"; _pinterest_pfob=disabled; _pinterest_sess=\"" + cookieSession + "\"; csrftoken=" + cookieCSRFToken as String
requestTwo.setValue(cookieRequest as String, forHTTPHeaderField: "Cookie")
let taskTwo = sessionOne.dataTask(with: requestTwo, completionHandler: { (data, response, error) in
if let error = error {
print("ERROR: \(error)")
}
else {
print("RESPONSE: \(response)")
} // end: if
})
taskTwo.resume()
PlaygroundPage.current.needsIndefiniteExecution = true
You can configure a cookie based session in the following way. Please let me know if you need any help. The below is just an example
let session: URLSession = URLSession.shared
session.dataTask(with: myUrlRequest { urlData, response, responseError in
let httpRes: HTTPURLResponse = (response as? HTTPURLResponse)!
let cookies:[HTTPCookie] = HTTPCookie.cookies(withResponseHeaderFields: httpRes.allHeaderFields as! [String : String], for: httpRes.url!)
HTTPCookieStorage.shared.setCookies(cookies, for: response?.url!, mainDocumentURL: nil)
if responseError == nil {
}else {
}
}.resume()
Feel free to suggest edits to make it better. Please let me know if the below doesn't work.
When you do an authentication with the server, the server gives out the cookies, which you receives in the header of the response. You can extract that and set as a cookie in the shared storage of cookies. So everytime you make a call, for those domain, the cookies will be shared and checked, and if valid, it will let you in.
let resp: HTTPURLResponse = (response as? HTTPURLResponse)!
let cookies:[HTTPCookie] = HTTPCookie.cookies(withResponseHeaderFields: resp.allHeaderFields as! [String : String], for: resp.url!)
HTTPCookieStorage.shared.setCookies(cookies, for: response?.url!, mainDocumentURL: nil)
In this, the cookies will be an array, which contain cookies in an array. An response may contain more than one cookies.
In case, if you are using third party framework to manage the HTTP requests like Alamofire then it will take cares of the cookie management itself.

Swift 2 How do you add authorization header to POST request

When making a particular POST request, Firefox (FF) dev tools shows a req. header named "Authorization" with a value of "Bearer X" where X is the access token received upon login. When I edit this request in FF and remove the "Authorization" line, I get a 400 error. When I put it back in, 200 and all is well. I haven't yet, however, figured out how to set this request header programmatically without getting 400.
Also, FF tools as a "Request Body" of {"source":"desktop - profile 2015"}. I'm assuming this is JSON. I've tried posting this in several ways (see code) but have had no success.
// the following fields are set in the object "Request"'s initialization
let accessToken = "1,2,3456789012,3x4f560fa7a89e01a2;33ab4b4e5e67e8e9b9f0e1a23db45678f9a9a0ff" // replaced some characters for this StackOF posting
let authorization = "Bearer \(accessToken)"
let method = "POST"
let userID = "1234567"
let URL = NSURL(string: "https://www.somesite.com/apitun/profile/\(userID)hide")
// tried setting params to all of the following 4:
let params = ""
let params = "&_json={}"
let params = "&_json={\"source\":\"desktop profile - 2015\"}
let params = "&_json=%7B%22source%22%3A%22desktop%2Dprofile%202015%22%7D"
func execute() {
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: URL)
if authorization != "" {
request.addValue(authorization, forHTTPHeaderField: "Authorization")
}
request.HTTPMethod = self.method
request.HTTPBody = self.params.dataUsingEncoding(NSUTF8StringEncoding)
self.task = session.dataTaskWithRequest(request) {
(data, response, error) in
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookies(self.cookies, forURL: self.URL, mainDocumentURL: nil)
if error == nil {
do {
self.responseHeaders = response as! NSHTTPURLResponse
self.cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(self.URL)!
self.statusCode = self.responseHeaders.statusCode
switch self.statusCode {
case 200:
self.contentsOfURL = try NSString(contentsOfURL: self.URL, encoding: NSUTF8StringEncoding)
case 400:
print("400: page not found")
case 404:
print("404: page not found")
case 407:
print("407: failed authenticate proxy credentials")
default:
print("unable to get statusCode")
}
} catch {
}
self.isRequesting = false
} else {
print(error)
}
}
self.task.resume()
}
let request = NSMutableURLRequest(URL: NSURL(string: fullURL)!)
let accessToken = "your access token"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

Resources