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)
}
Related
After URLSession.shared.dataTask it's not either returning error or success.
The completion handler is not getting called. How can I check or how can I proceed further. There is no error the app is working as such, but without data on the screen which is displayed.
func getPromotionsData() {
ConnectionManager.sharedInstance()?.getPromotions(PROMOTIONS, withCompletion: {
result, error in
if let result = result {
print("result: \(result)")
}
var arrPromotions: [Any] = []
if let object = result?["promotions"] as? [Any] {
arrPromotions = object
}
self.dataSource = []
if let arrPromotions = arrPromotions as? [AnyHashable] {
self.dataSource = arrPromotions
}
DispatchQueue.main.async(execute: {
self.collectionView.reloadData()
})
})
}
func getPromotions(_ path: String?, withCompletion completion: #escaping (_ result: [AnyHashable : Any]?, _ error: Error?) -> Void) {
let strPath = "/\(API)/\(path ?? "").json"
let url = strPath.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
makeRequest(BASE_URL, path: url, httpMethod: GET_METHOD, httpBody: nil, completion: completion)
}
func makeRequest(_ url: String?, path: String?, httpMethod: String?, httpBody httpBoday: Data?, completion: #escaping (_ result: [AnyHashable : Any]?, _ error: Error?) -> Void) {
let headers = [
"cache-control": "no-cache",
"Authorization": "Token f491fbe3ec54034d51e141e28aaee87d47bb7e74"
]
var request: URLRequest? = nil
if let url = URL(string: "\(url ?? "")\(path ?? "")") {
request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
}
request?.httpMethod = httpMethod ?? ""
request?.allHTTPHeaderFields = headers
let configuration = URLSessionConfiguration.default
configuration.httpCookieStorage = nil
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
if #available(iOS 11.0, *) {
configuration.waitsForConnectivity = false
}
let session = URLSession(configuration: configuration)
// let session = URLSession.shared
var task: URLSessionDataTask? = nil
print ("Request =======>",request)
if let req = request {
task = session.dataTask(with: request! , completionHandler: {
data, response, error in
var result: Any? = nil
if error != nil {
if let error = error {
print("\(error)")
}
if completion != nil {
completion(nil, error)
}
} else
{
var string: String? = nil
if let data = data {
string = String(data: data, encoding: .utf8)
}
string = self.string(byRemovingControlCharacters: string)
do {
if let data = string?.data(using: .utf8) {
result = try JSONSerialization.jsonObject(with: data, options: []) as! [AnyHashable : Any]
print ("Result ===============>",result)
}
} catch {
}
if completion != nil {
completion(result as! [AnyHashable : Any], error)
}
}
})
}
task?.resume()
}
Actually the completion block is an asynchronous process and i was waiting for the control to go back immediately after the process ends in debugging mode. It works now as expected
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()
On certain websites the below method hangs indefinitely, I'd like to cancel the request when its taking too long, I thought that timeoutIntervalForRequest and timeoutIntervalForResource would control this but setting either of them doesn't seem to have any effect.
This is an example website that the request hangs indefinitely on: http://www.samtoft.co.uk/showpic.php?id=542&order=&search=#header
// fetch data from URL with NSURLSession
class func getDataFromServerWithSuccess(myURL: String, noRedirect: Bool, callback: Result<String> -> Void) {
var loadDataTask: NSURLSessionDataTask? = nil
let sessionConfig: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
sessionConfig.timeoutIntervalForRequest = 10.0
sessionConfig.timeoutIntervalForResource = 10.0
var myDelegate: MySession? = nil
if noRedirect {
myDelegate = MySession()
}
let session = NSURLSession(configuration: sessionConfig, delegate: myDelegate, delegateQueue: nil)
loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if let checkedData = data {
let success = Result.Success(NSString(data: checkedData, encoding: NSASCIIStringEncoding) as! String)
callback(success)
} else {
let redirectError = NetworkError.FailedUrl("\(myURL) + \(error)")
if let request = loadDataTask?.currentRequest {
guard let urlExtension = request.URL?.pathExtension else {return}
guard let domain = request.URL?.host else {return}
guard let finalURLAsString = request.URL?.description else {return}
let failure = Result.Failure("\(finalURLAsString) + \(redirectError)")
callback(failure)
}
}
}
loadDataTask!.resume()
}
EDIT: for anyone who is having the same issue I was, the problem was that I was accounting for the success case and the error case, but not for when the request returned no response, so I added the below:
if response == nil {
print("NO RESPONSE \(myURL)")
let noResponse = Result.NoResponse("NO RESPONSE")
callback(noResponse)
}
I have extracted your code as
func getDataFromServerWithSuccess(myURL: String) {
var loadDataTask: NSURLSessionDataTask? = nil
let sessionConfig: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
//sessionConfig.timeoutIntervalForRequest = 11.0
sessionConfig.timeoutIntervalForResource = 11.0
let session = NSURLSession(configuration: sessionConfig)
loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
print("end")
}
loadDataTask!.resume()
}
It does work sensitively for the timeout. I suppose there may be something wrong with the delegator which cannot receive the response properly. Hope this helps.
I read a lot of post on SO on how I could be able to apply basic authentication.
I've produced this code but it does not show the log on page, only a white page is displayed. Credentials that I use works in the browser, so this is not the problem. My delegates are ok.
I can't figure out where my code fails:
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
self.loadPage()
}
func loadPage() {
let url = "mypage.com/auht/Logon.do"
let request = NSMutableURLRequest(URL: NSURL(string: url)!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 12)
webView.loadRequest(request)
}
// MARK: NSURLConnectionDelegate Delegates
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.previousFailureCount == 0 {
authenticated = true
let credential = NSURLCredential(user: "m.rinaldi13", password: "299792,458km/s", persistence: NSURLCredentialPersistence.ForSession)
challenge.sender.useCredential(credential, forAuthenticationChallenge: challenge)
} else {
challenge.sender.cancelAuthenticationChallenge(challenge)
}
}
// MARK: Web View Delegates
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if authenticated == nil {
authenticated = false
NSURLConnection(request: request, delegate: self)
return false
}
return true
}
Any help/tip will be appreciated!
I find a solution by my self, excluding all this boring passeges.
func doRequestWithBasicAuth(completion : (success : Bool, html: String?, error : NSError?) -> Void) {
if let user = self.user {
let loginString = NSString(format: "%#:%#", user.login!, user.pwd!)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(nil)
let url = NSURL(string: user.service!.getURL())
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
if error == nil {
let htmlString = NSString(data: data, encoding: NSUTF8StringEncoding)
completion(success: true, html: htmlString as? String, error: nil)
} else {
completion(success: false, html: nil, error: error)
}
}
} else {
completion(success: false, html: nil, error: NSError())
}
}
Then you can evenly display page on web view in this way:
self.doRequestWithBasicAuth({ (success, html, error) -> Void in
if error == nil {
self.webView.loadHTMLString(string: html, baseURL: <yourNSURL>)
}
})
Obviously you can (had) to beautify code, like creating a class for model User:
class User {
var login: String?
var pwd: String?
func valueForHeaderFieldAuthorization() -> String {
let loginString = NSString(format: "%#:%#", user.login!, user.pwd!)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(nil)
return "Basic \(base64LoginString)"
}
}
I ported some objective-c code (with some changes) to swift and the link w/o "private" in it works and the other doesn't. Here"s what I have:
import Cocoa
class MasterViewController: NSViewController, NSURLSessionDataDelegate {
var session: NSURLSession!
var courses: JSON!
override func viewDidLoad() {
super.viewDidLoad()
let config: NSURLSessionConfiguration? =
NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil )
fetchFeed()
}
func fetchFeed() {
let requestString: String = "https://bookapi.bignerdranch.com/courses.json"
// let requestString: String = "https://bookapi.bignerdranch.com/private/courses.json"
if let url: NSURL? = NSURL(string: requestString) {
let request: NSURLRequest = NSURLRequest(URL: url!)
let dataTask : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var jsonObject: [JSON] = JSON(data: data).arrayValue
self.courses = JSON(data: data)
dispatch_async(dispatch_get_main_queue(), { _ in
println( self.courses?.debugDescription )
})
});
dataTask.resume()
}
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) {
var userIdString: String = "BigNerdRanch"
var passwordString: String = "AchieveNerdvana"
println( "Received challenge, responding with UID: \(userIdString) PWD: \(passwordString)")
var cred: NSURLCredential = NSURLCredential(user: userIdString,
password: passwordString,
persistence: NSURLCredentialPersistence.ForSession)
completionHandler( NSURLSessionAuthChallengeDisposition.UseCredential, cred )
}
}
Any ideas?
Thanks
If you comment out the URL without "private" in it and uncomment the URL wit "private" in it, it does not work. If you do it from a web browser (Chrome,Firefox,Safari, etcetera) "https://bookapi.bignerdranch.com/private/courses.json" enter the credentials "BigNerdRanch" for user ID "AchieveNerdvana" for password and it returns the expected JSON