Search code examples
swiftalamofire

How To Make Authenticated HTTPS Post In Swift?


In an iPhone app, I'd like to collect JSON output from an authenticated HTTPS post of image data. That is, something equivalent to:

curl -u "username":"password" \
     -X POST \
     -F "[email protected]" \
     "https://server.com/blah?param=value"

This command is verified to work with the real URL and not this dummy one given for the sake of asking a question. The server expects basic authentication.

Following the "Uploading MultipartFormData" example in the Alamofire readme and this stackoverflow example, my first attempt uses Alamofire like so:

// 'username' and 'password' are string literals. 'img' is a valid UIImage.
let url =  NSURL("https://server.com/blah?param=value")
let dat = UIImagePNGRepresentation(img)
let credentialData = "\(username):\(password)".dataUsingEncoding(NSUTF8StringEncoding)!
let base64Credentials = credentialData.base64EncodedStringWithOptions([])
let headers = ["Authorization": "Basic \(base64Credentials)"]
Alamofire.upload(
        Alamofire.Method.POST,
        url!,
        headers: headers,
        multipartFormData: { multipartFormData in
            multipartFormData.appendBodyPart(data:dat!, name:"file")
        },
        encodingCompletion: { encodingResult in
            switch encodingResult {

            case .Success(let upload, _, _):
                upload.responseString { response in
                    //JSON = response.result.value! as String
                    debugPrint(response)
                }
            case .Failure(let encodingError):
                print(encodingError)
            }
        })

Again, this code snippet uses the dummy url but I run the code with the real url. This leads to an SSL error:

2015-12-31 05:28:34.733 AutoAlbum[13518:249313] _BSMachError: (os/kern) invalid capability (20)
2015-12-31 05:28:34.733 AutoAlbum[13518:249682] _BSMachError: (os/kern) invalid name (15)
2015-12-31 05:28:35.413 AutoAlbum[13518:249708] CFNetwork SSLHandshake failed (-9824)
2015-12-31 05:28:35.415 AutoAlbum[13518:249708] NSURLSession/NSURLConnection  HTTP load failed (kCFStreamErrorDomainSSL, -9824)
[Request]: <NSMutableURLRequest: 0x7fdeda835040> { URL: https://server.com/blah?param=value }
[Response]: nil
[Data]: 0 bytes
[Result]: FAILURE: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9824, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fdeda84bd90 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://server.com/blah?param=value, NSErrorFailingURLStringKey=https://server.com/blah?param=value, _kCFStreamErrorDomainKey=3}

This error happens on xcode's iPhone 5s simulator, when there is a network connection.

My second approach is:

let PasswordString = "username:password"
let PasswordData = PasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = PasswordData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
let urlPath: String = "https://server.com/blah?param=value"
let url: NSURL = NSURL(string: urlPath)!
var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "POST"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders = ["Authorization" : authString]
let session = NSURLSession(configuration: config)
let img:UIImage = getAsset(asset, size: PHImageManagerMaximumSize)
let dat = String(UIImagePNGRepresentation(img))
let qstr = "file=\(dat)"
request.HTTPBody = qstr.dataUsingEncoding(NSUTF8StringEncoding)
var dataString = ""
session.dataTaskWithRequest(request) {
    (let data, let response, let error) in
    if let httpResponse = response as? NSHTTPURLResponse {
        dataString = String(data: data!, encoding: NSUTF8StringEncoding)!
        print(dataString)
    }
    else {
        print(error)
    }
}.resume()

This led to the same ssl error:

AutoAlbum[15614:316266] CFNetwork SSLHandshake failed (-9824)
2015-12-31 15:47:51.142 AutoAlbum[15614:316266] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." 

Following this example, I tried setting the minimum tls version to 1.0 like so: enter image description here

but ran into the same errors.

Can anybody spot a problem in my code, or does anybody have a recommendation for how to use iOS technologies to do what that curl command does?


Solution

  • It's not about authentication, iOS can't establish https connection to server for some reasons (Code -1200 means "Returned when an attempt to establish a secure connection fails for reasons which cannot be expressed more specifically").

    Curl can perform this request because it doesn't perform strict checking like ATS do, so you should properly configure it, and you tried to do it. But you have some errors in ATS configuration, that's why request still failing.

    1. NSAllowsArbitraryLoads should have boolean type, not string

    2. NSExceptionMinimumTLSVersion should be embedded inside dictionary with domain name (Look carefully at your example, you miss part when you should replace yourserver.com to actual domain name). This is a link to official documentation how to properly configure exceptions for ATS, it's provide more details about how it works.

    3. Also, specifiying NSAllowsArbitraryLoads and NSExceptionMinimumTLSVersion for specific domain is redundant, because NSAllowsArbitraryLoads will override this setting.