Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 3 URLSession with URLCredential not working

Tags:

ios

swift

swift3

I am doing a URLSession, but the URL requires credentials.

I have this whole method here that is trying to do a URLSession with URLCredentials:

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

        credential = URLCredential(user:username, password:password, persistence: .forSession)

        //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: {

                if(error == nil)
                {
                    completion(true)
                }
                else
                {
                    completion(false)
                }

            })

        })

        //Run the task to get data.

        task.resume()

    }

and here are my URLSessionDelegate Methods:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        if challenge.previousFailureCount > 0
        {
            completionHandler(Foundation.URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
        }
        else
        {
            completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:challenge.protectionSpace.serverTrust!))
        }

    }

    /**
     Requests credentials from the delegate in response to an authentication request from the remote server.
     */

    func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential,credential)

    }

I notice when I debug this in this delegate method:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        if challenge.previousFailureCount > 0
        {
            completionHandler(Foundation.URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
        }
        else
        {
            completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:challenge.protectionSpace.serverTrust!))
        }

    }

That this method gets called twice and when it hits this line for the second time:

completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:challenge.protectionSpace.serverTrust!))

I get this error:

fatal error: unexpectedly found nil while unwrapping an Optional value

and then my app crashes! How do I fix this error?

like image 394
user979331 Avatar asked Nov 07 '16 16:11

user979331


3 Answers

the crash is due to challenge.protectionSpace.serverTrust being nil when you attempt to force unwrap it.

you should unwrap serverTrust and handle it being nil. my guess is that when serverTrust is nil challenge.error has a value.

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    if challenge.previousFailureCount > 0 {
        completionHandler(Foundation.URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    } else if let serverTrust = challenge.protectionSpace.serverTrust {
        completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: serverTrust))
    } else {
        print("unknown state. error: \(challenge.error)")
        // do something w/ completionHandler here
    }
}
like image 157
Casey Avatar answered Nov 02 '22 08:11

Casey


Here is syntax as per swift 3.

Just verify firstly in which part of the delegate method it entered

open func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void){

    var disposition: URLSession.AuthChallengeDisposition = URLSession.AuthChallengeDisposition.performDefaultHandling

    var credential:URLCredential?

    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
    }

    if (completionHandler != nil) {
        completionHandler(disposition, credential);
    }
}
like image 29
Ankit Thakur Avatar answered Nov 02 '22 07:11

Ankit Thakur


Here is a slightly refactored from the above answers, a delegate class checks the number of failures, uses default unless the challenge is of type server trust it then calls the completion with trust credential:

class AuthSessionDelegate: NSObject, URLSessionDelegate {

func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    let authMethod = challenge.protectionSpace.authenticationMethod

    guard challenge.previousFailureCount < 1, authMethod == NSURLAuthenticationMethodServerTrust,
        let trust = challenge.protectionSpace.serverTrust else {
        completionHandler(.performDefaultHandling, nil)
        return
    }

    completionHandler(.useCredential, URLCredential(trust: trust))
}

}

like image 1
Jess Avatar answered Nov 02 '22 08:11

Jess