118

I have a RESTFull service with basic authentication and I want to invoke it from iOS+swift. How and where I must provide Credential for this request?

My code (sorry, I just start learn iOS/obj-c/swift):

class APIProxy: NSObject {
    var data: NSMutableData = NSMutableData()
    
    func connectToWebApi() {
        var urlPath = "http://xx.xx.xx.xx/BP3_0_32/ru/hs/testservis/somemethod"
        NSLog("connection string \(urlPath)")
        var url: NSURL = NSURL(string: urlPath)
        var request = NSMutableURLRequest(URL: url)
        let username = "hs"
        let password = "1"
        let loginString = NSString(format: "%@:%@", username, password)
        let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)
        let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.fromMask(0))
        request.setValue(base64LoginString, forHTTPHeaderField: "Authorization")
        
        var connection: NSURLConnection = NSURLConnection(request: request, delegate: self)
        
        connection.start()
    }
    
    
    //NSURLConnection delegate method
    func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
        println("Failed with error:\(error.localizedDescription)")
    }
    
    //NSURLConnection delegate method
    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
        //New request so we need to clear the data object
        self.data = NSMutableData()
    }
    
    //NSURLConnection delegate method
    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
        //Append incoming data
        self.data.appendData(data)
    }
    
    //NSURLConnection delegate method
    func connectionDidFinishLoading(connection: NSURLConnection!) {
        NSLog("connectionDidFinishLoading");
    }
    
}
de.
  • 7,068
  • 3
  • 40
  • 69
MrKos
  • 1,660
  • 2
  • 17
  • 18
  • BTW, `NSURLConnection(request: request, delegate: self)` will `start` the connection for you. Do not call `start` method explicitly yourself, effectively starting it a second time. – Rob Jul 20 '15 at 20:47
  • 3
    NSURLConnection is deprecated. You should really switch to NSURLSession. – Sam Soffes Dec 17 '15 at 17:54

9 Answers9

204

You provide credentials in a URLRequest instance, like this in Swift 3:

let username = "user"
let password = "pass"
let loginString = String(format: "%@:%@", username, password)
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()

// create the request
let url = URL(string: "http://www.example.com/")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

// fire off the request
// make sure your class conforms to NSURLConnectionDelegate
let urlConnection = NSURLConnection(request: request, delegate: self)

Or in an NSMutableURLRequest in Swift 2:

// set up the base64-encoded credentials
let username = "user"
let password = "pass"
let loginString = NSString(format: "%@:%@", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions([])

// create the request
let url = NSURL(string: "http://www.example.com/")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

// fire off the request
// make sure your class conforms to NSURLConnectionDelegate
let urlConnection = NSURLConnection(request: request, delegate: self)
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • request.setValue(base64LoginString, forHTTPHeaderField: "Authorization") => request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization") i have added word "Basic" and it work for me well – MrKos Jun 24 '14 at 09:53
  • 4
    'NSDataBase64EncodingOptions.Type' does not have a member named 'fromMask'..This is the error I get in Xcode 6.1..Pls help..What is mask(0) – Bala Vishnu Oct 27 '14 at 10:18
  • 2
    I am also seeing the same message as @BalaVishnu in xCode but I just used .allZeros instead – Sean Larkin Dec 03 '14 at 21:01
  • 1
    Swift's syntax for option sets changed in Xcode 1.1. You can use `NSDataBase64EncodingOptions(0)` or `nil` for no options. Updated the answer. – Nate Cook Dec 03 '14 at 21:36
  • Anyone knows why it is discouraged in Apple's documentation to change the `Authorisation` header in `NSMutableURLRequest`? Here's Apple's code: _The URL Loading System is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers using the `addValue:forHTTPHeaderField:` or `setValue:forHTTPHeaderField:` methods: Authorization, ..._ [See Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSMutableURLRequest_Class/) – Andrej Mar 26 '16 at 20:52
  • Is this answer still valid for Swift 3.0? – acoustic_north Sep 25 '16 at 23:00
  • This is discouraged because Apple wants people to use the challenge methods they have provided. That would be better because you only send the authorization when it's required by the requested URL. Meaning you wouldn't send it when a URL doesn't request it. Boosting security... – keji Mar 25 '17 at 22:46
  • @NateCook you rock! – Anurag Sharma May 06 '17 at 14:47
29

swift 4:

let username = "username"
let password = "password"
let loginString = "\(username):\(password)"

guard let loginData = loginString.data(using: String.Encoding.utf8) else {
    return
}
let base64LoginString = loginData.base64EncodedString()

request.httpMethod = "GET"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
Amr
  • 2,160
  • 1
  • 15
  • 8
23

//create authentication base 64 encoding string

    let PasswordString = "\(txtUserName.text):\(txtPassword.text)"
    let PasswordData = PasswordString.dataUsingEncoding(NSUTF8StringEncoding)
    let base64EncodedCredential = PasswordData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
    //let base64EncodedCredential = PasswordData!.base64EncodedStringWithOptions(nil)

//create authentication url

    let urlPath: String = "http://...../auth"
    var url: NSURL = NSURL(string: urlPath)

//create and initialize basic authentication request

    var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
    request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
    request.HTTPMethod = "GET"

//You can use one of below methods

//1 URL request with NSURLConnectionDataDelegate

    let queue:NSOperationQueue = NSOperationQueue()
    let urlConnection = NSURLConnection(request: request, delegate: self)
    urlConnection.start()

//2 URL Request with AsynchronousRequest

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
        println(NSString(data: data, encoding: NSUTF8StringEncoding))
    }

//2 URL Request with AsynchronousRequest with json output

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
        var err: NSError
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        println("\(jsonResult)")
    })

//3 URL Request with SynchronousRequest

    var response: AutoreleasingUnsafePointer<NSURLResponse?>=nil
    var dataVal: NSData =  NSURLConnection.sendSynchronousRequest(request, returningResponse: response, error:nil)
    var err: NSError
    var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataVal, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
    println("\(jsonResult)")

//4 URL Request with NSURLSession

    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    let authString = "Basic \(base64EncodedCredential)"
    config.HTTPAdditionalHeaders = ["Authorization" : authString]
    let session = NSURLSession(configuration: config)

    session.dataTaskWithURL(url) {
        (let data, let response, let error) in
        if let httpResponse = response as? NSHTTPURLResponse {
            let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
            println(dataString)
        }
    }.resume()

// you may be get fatal error if you changed the request.HTTPMethod = "POST" when server request GET request

Subhash
  • 545
  • 7
  • 11
  • 2
    BTW, this repeats the mistake in the OP's code: `NSURLConnection(request: request, delegate: self)` starts the request. You should not `start` it a second time. – Rob Jul 20 '15 at 20:48
7

In Swift 2:

extension NSMutableURLRequest {
    func setAuthorizationHeader(username username: String, password: String) -> Bool {
        guard let data = "\(username):\(password)".dataUsingEncoding(NSUTF8StringEncoding) else { return false }

        let base64 = data.base64EncodedStringWithOptions([])
        setValue("Basic \(base64)", forHTTPHeaderField: "Authorization")
        return true
    }
}
Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
4

go plain for SWIFT 3 and APACHE simple Auth:

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

    let credential = URLCredential(user: "test",
                                   password: "test",
                                   persistence: .none)

    completionHandler(.useCredential, credential)


}
ingconti
  • 10,876
  • 3
  • 61
  • 48
2

I had a similar problem trying to POST to MailGun for some automated emails I was implementing in an app.

I was able to get this working properly with a large HTTP response. I put the full path into Keys.plist so that I can upload my code to github and broke out some of the arguments into variables so I can have them programmatically set later down the road.

// Email the FBO with desired information
// Parse our Keys.plist so we can use our path
var keys: NSDictionary?

if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
    keys = NSDictionary(contentsOfFile: path)
}

if let dict = keys {
    // variablize our https path with API key, recipient and message text
    let mailgunAPIPath = dict["mailgunAPIPath"] as? String
    let emailRecipient = "bar@foo.com"
    let emailMessage = "Testing%20email%20sender%20variables"

    // Create a session and fill it with our request
    let session = NSURLSession.sharedSession()
    let request = NSMutableURLRequest(URL: NSURL(string: mailgunAPIPath! + "from=FBOGo%20Reservation%20%3Cscheduler@<my domain>.com%3E&to=reservations@<my domain>.com&to=\(emailRecipient)&subject=A%20New%20Reservation%21&text=\(emailMessage)")!)

    // POST and report back with any errors and response codes
    request.HTTPMethod = "POST"
    let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
        if let error = error {
            print(error)
        }

        if let response = response {
            print("url = \(response.URL!)")
            print("response = \(response)")
            let httpResponse = response as! NSHTTPURLResponse
            print("response code = \(httpResponse.statusCode)")
        }
    })
    task.resume()
}

The Mailgun Path is in Keys.plist as a string called mailgunAPIPath with the value:

https://API:key-<my key>@api.mailgun.net/v3/<my domain>.com/messages?

Hope this helps offers a solution to someone trying to avoid using 3rd party code for their POST requests!

1

my solution works as follows:

import UIKit


class LoginViewController: UIViewController, NSURLConnectionDataDelegate {

  @IBOutlet var usernameTextField: UITextField
  @IBOutlet var passwordTextField: UITextField

  @IBAction func login(sender: AnyObject) {
    var url = NSURL(string: "YOUR_URL")
    var request = NSURLRequest(URL: url)
    var connection = NSURLConnection(request: request, delegate: self, startImmediately: true)

  }

  func connection(connection:NSURLConnection!, willSendRequestForAuthenticationChallenge challenge:NSURLAuthenticationChallenge!) {

    if challenge.previousFailureCount > 1 {

    } else {
        let creds = NSURLCredential(user: usernameTextField.text, password: passwordTextField.text, persistence: NSURLCredentialPersistence.None)
        challenge.sender.useCredential(creds, forAuthenticationChallenge: challenge)

    }

}

  func connection(connection:NSURLConnection!, didReceiveResponse response: NSURLResponse) {
    let status = (response as NSHTTPURLResponse).statusCode
    println("status code is \(status)")
    // 200? Yeah authentication was successful
  }


  override func viewDidLoad() {
    super.viewDidLoad()

  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()

  }  
}

You can use this class as the implementation of a ViewController. Connect your fields to the IBOutlet annotated vars and your Button to the IBAction annotated function.

Explanation: In function login you create your request with NSURL, NSURLRequest and NSURLConnection. Essential here is the delegate which references to this class (self). For receiving the delegates calls you need to

  • Add the protocol NSURLConnectionDataDelegate to the class
  • Implement the protocols' function "connection:willSendRequestForAuthenticationChallenge" This is used for adding the credentials to the request
  • Implement the protocols' function "connection:didReceiveResponse" This will check the http response status code
Oliver Koehler
  • 711
  • 2
  • 8
  • 24
1

I am calling the json on login button click

@IBAction func loginClicked(sender : AnyObject){

var request = NSMutableURLRequest(URL: NSURL(string: kLoginURL)) // Here, kLogin contains the Login API.


var session = NSURLSession.sharedSession()

request.HTTPMethod = "POST"

var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(self.criteriaDic(), options: nil, error: &err) // This Line fills the web service with required parameters.
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
 //   println("Response: \(response)")
var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)")       
var err1: NSError?
var json2 = NSJSONSerialization.JSONObjectWithData(strData.dataUsingEncoding(NSUTF8StringEncoding), options: .MutableLeaves, error:&err1 ) as NSDictionary

println("json2 :\(json2)")

if(err) {
    println(err!.localizedDescription)
}
else {
    var success = json2["success"] as? Int
    println("Succes: \(success)")
}
})

task.resume()

}

Here, I have made a seperate dictionary for the parameters.

var params = ["format":"json", "MobileType":"IOS","MIN":"f8d16d98ad12acdbbe1de647414495ec","UserName":emailTxtField.text,"PWD":passwordTxtField.text,"SigninVia":"SH"]as NSDictionary
     return params
}
siledh
  • 3,268
  • 2
  • 16
  • 29
Programming Learner
  • 4,351
  • 3
  • 22
  • 34
1

Working example for SwiftUI iOS15 async/await

   struct ExampleJSONService {
        
        let passwordString = "user:password"
        let configuration = URLSessionConfiguration.default
        
        enum ExampleJSONServiceError: Error {
            case failed
            case failedToDecode
            case invalidStatusCode
        }
        
        func fetchStuff(for myID:String) async throws -> [Stuff] {
            
            let passwordData = passwordString.data(using:String.Encoding.utf8)!
            let base64EncodedCredential = passwordData.base64EncodedString()
            let authString = "Basic \(base64EncodedCredential)"
            let session = URLSession(configuration: configuration)
            
            configuration.httpAdditionalHeaders = ["Authorization" : authString]
            
            let dataUrl = "https://toto.org/stuff/\(myID)/data.json"
            
            let url = URL(string: dataUrl)!
            
            var urlRequest = URLRequest(url: url)
            
            urlRequest.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
            urlRequest.httpMethod = "GET"
            
            let (data, response) = try await session.data(for: urlRequest)
            
            guard let response = response as? HTTPURLResponse,
    
                  response.statusCode == 200  else  {
                      throw PrixJSONServiceError.invalidStatusCode
                  }
            
            let decodedData = try JSONDecoder().decode([Prix].self, from: data)
    
            return decodedData
        }
        
    }
jat
  • 183
  • 3
  • 14