NSURLSession Authentication Swift

Hello developers,

hope, someone can help me with authentication using NSURLSession. In the first of the two following code example, "didReceiveAuthenticationChallenge" never get's called. So I can't authenticate and get a "401 unauthorized". What am I doing wrong?


Thanks in advance,

Elmo 🙂


This doesn't work ("didReceiveAuthenticationChallenge" never get's called):


class ViewController: UIViewController, NSURLSessionDelegate {

override func viewDidLoad() {
super.viewDidLoad()
getData()
}

private var request: NSMutableURLRequest {
let baseUrl = "http://myRestCall....."
var url = NSURL(string: baseUrl)!
return NSMutableURLRequest(URL: url)
}
func getData() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
task.resume()
}

func URLSession(session: NSURLSession, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
{
println("didReceiveAuthenticationChallenge")
let user = "myUsername"
let password = "myPassword"
let credential = NSURLCredential(user: user, password: password, persistence: NSURLCredentialPersistence.ForSession)
challenge.sender.useCredential(credential, forAuthenticationChallenge: challenge)
}

}


The following code (with NSURLConnection) works fine (didReceiveAuthenticationChallenge" get's called):


class ViewController: UIViewController, NSURLConnectionDelegate{

override func viewDidLoad() {

super.viewDidLoad()

var conn = NSURLConnection(request: request, delegate: self, startImmediately: true)

}


private var request: NSMutableURLRequest {

let baseUrl = http://myRestCall ...

var url = NSURL(string: baseUrl)!

return NSMutableURLRequest(URL: url)

}


func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool

{

return true

}


func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge)

{

println("didReceiveAuthenticationChallenge")

let user = "myUsername"

let password = "myPassword"


let credential = NSURLCredential(user: user, password: password, persistence: NSURLCredentialPersistence.ForSession)

challenge.sender.useCredential(credential, forAuthenticationChallenge: challenge)

}

}

If this is a username / password authentication challenge (typically

NSURLAuthenticationMethodDefault
,
NSURLAuthenticationMethodHTTPBasic
,
NSURLAuthenticationMethodHTTPDigest
) then the problem is that you've implemented the delegate callback method for session challenges (
-URLSession:didReceiveChallenge:completionHandler:
) not task challenges (
-URLSession:task:didReceiveChallenge:completionHandler:
).

NSURLSession makes a clear distinction between different types of challenges:

Session challenges are those that are caused by a connection within the session. The TLS challenges fall into this category (

NSURLAuthenticationMethodClientCertificate
and
NSURLAuthenticationMethodServerTrust
), as well as NTLM (
NSURLAuthenticationMethodNTLM
).

Session challenges are delivered via

-URLSession:didReceiveChallenge:completionHandler:
. If that's not implemented, the challenge is sent to
-URLSession:task:didReceiveChallenge:completionHandler:
with the task that triggered the connection that triggered the challenge.

Task challenges are those that are triggered by a specific request.

Request challenges are always sent to

-URLSession:task:didReceiveChallenge:completionHandler:
; there's no fallback.

This is documented properly in the NSURLSession reference documentation but the discussion in the high-level docs (the Authentication Challenges and TLS Chain Validation section of the URL Loading System Programming Guide) is very poor. I've filed a bug to get that fixed.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn for your answer.

Now I tried the following code and didReceiveChallenge still isn't called.

Maybe my Implementation for -URLSession:task:didReceiveChallenge:completionHandler is wrong (e.g. is it really didReceiveChallenge and not didReceiveAuthenticationChallenge?).


class ViewController: UIViewController, NSURLSessionTaskDelegate {

override func viewDidLoad() {
super.viewDidLoad()
getData()
}


private var request: NSMutableURLRequest {
let baseUrl = "..."
var url = NSURL(string: baseUrl)!
return NSMutableURLRequest(URL: url)
}

func getData() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
task.resume()
}

func URLSession(session: NSURLSession,
task task: NSURLSessionTask,
didReceiveChallenge challenge: NSURLAuthenticationChallenge,
completionHandler completionHandler: (NSURLSessionAuthChallengeDisposition,
NSURLCredential?))
{
println("didReceiveAuthenticationChallengex")
}

}

Accepted Answer

This is definitely a problem with how your delegate method signature is set up. It's hard to say what the exact problem is because it's different in Xcode 6 and Xcode 7. I can, however, tell you a general recipe for avoiding problems like this:

  1. configure the class to conform to the right delegate protocol

  2. inside the class, type

    URLSession
    and hit Escape get code completion
  3. select the right method from the list

If you do this then Xcode will always insert the delegate method with the correct signature.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hey eskimo: Amazing idea to use autocomplete. ;-) Thanks a lot. Now it works. Correct method signature when working with NSURLSessionTaskDelegate is:


func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) {

println("didReceiveAuthenticationChallenge")

}

NSURLSession Authentication Swift
 
 
Q