Swift - HTTP digest auth - ios

I am currently in the process of reverse engineering a home automation API. I want to manage all settings with my own app - because there is really no current home automation app of the company.
Anyway - I already managed the authentication with my SmartHome device. To not make it too complicated: I need http digest authentication for final communication.
I have already been able to connect to my device through the command line with curl - unfortunately this doesn't work in Swift as planned.
curl -X POST -d '{"key": "value"}' https://192.168.0.0:1/action -k -s --digest --user username:password
Translated to Swift:
(1) Using Alamofire
import Alamofire
let data: [String: any] = ["key": "value"]
let request = Alamofire.request("https://192.168.0.0:1/action", method: HTTPMethod.post, parameters: data);
request.authenticate(user: "username", password: "password")
request.responseJSON { response in
// leads to error because of invalid self signed certificate of the smart home device ("https:")
}
Note to Alamofire: I guess using an external libary such as AF does not make much sense in this case - there are some unresolved issues that wont let such code as above work. (Self signed ceritficates makes problems, using custom manager instances overriding internal stuff leads also to problems) - I've already spent hours believe me.
(2) Using not Alamofire :)
extension ViewController: URLSessionDelegate {
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, urlCredential)
}
}
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
request.httpBody = jsonData;
let task = session.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
// success
}
}
task.resume()
} catch { }
The code above seems to work fine - the problem is that I've not implemented the digest authentication yet - because I do not find any method how to do this.
It would be super helpful if somebody to get some tips how generate the Auth header based on username and password
Edit
Curl uses this Authorization header:
> Digest username="username",
realm="XTV",
nonce="MTU5MDcNc2UxNjQ3OTo1YzMwYjc3YjIxMzAAAGQ5Nzg2NzUzMmRkZGU1ZVVlYw==",
uri="/action",
cnonce="MTExOTZlZmI1MjBlYWU0MTIzMDBmNDE0YTkWzJl1MDk=",
nc=00000001,
qop=auth,
response="2ba89269645e2aa24ac6f117d85e190c",
algorithm="MD5"
Is there the possibility to generate this header in Swift?

Digest authentication is supported automatically by URLSession and Alamofire through URLCredential (which is what authenticate() uses in Alamofire) when the server properly returns the WWW-Authenticate header with the proper digest settings.
You can generate the header manually, though I wouldn't recommend it due to the complexity of the digest process. I've found the wikipedia page to be thorough enough to implement the standard manually.

Related

URLSession doesn't pass 'Authorization' key in header swift 4

I am trying to pass authorization key in header of a URLRequest. But at the server end the key is not received. The same API when called from postman working fine. Any other key in the header is working fine, even authorizations key is visible at server end.
Here is my code:
let headers = [
"authorization": "token abcd"
]
var request = URLRequest.init(url: NSURL(string:
"http://127.0.0.1:7000/api/channels?filter=contributed")! as URL)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = headers
let session = URLSession.init(configuration: config)
let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error ?? "")
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse ?? "")
}
})
As you can see, I tried to set the token in both session config and request but none is working.
This seems to be working:
// Set the security header
private var credentials: String {
return "\(participantId):\(password)"
}
private var basicAuthHeader: String {
return "Basic \(credentials)"
}
func getSettings(participantId: Int, password: String) -> Bool {
self.participantId = participantId
self.password = password
let path = "/settings/\(participantId)"
guard let url = URL(string: "\(BASE_URL)\(path)") else {
Log.e("Invalid URL string, could not convert to URL")
return false
}
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue(basicAuthHeader, forHTTPHeaderField: "Authorization")
urlRequest.setValue(APP_FILE_NAME, forHTTPHeaderField: "User-Agent")
// This is a synchronous wrapper extension around URLSession.dataTask()
let (data, response, error) = URLSession.shared.synchronousDataTask(with: urlRequest)
// Process the result...
}
Note: code written by my coworker. Thanks John!
Looks like the problem is that you are modifying Authorization header using httpAdditionalHeaders which is something you should not do.
From the Doc
An NSURLSession object is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers:
Authorization,
Connection,
Host,
Proxy-Authenticate,
Proxy-Authorization,
WWW-Authenticate
Removing the line config.httpAdditionalHeaders = headers
should fix the issue.
If you want token to be hardcoded, I guess it has to be like this:
urlRequest.httpMethod = "GET"
urlRequest.setValue("Token <Your Token>", forHTTPHeaderField: "Authorization")
I found the same thing: setting the header field Authorization just didn't do the trick.
Here's the solution I settled on (which works well):
I added the URLSessionDelegate protocol to my current class. This unfortunately means inheriting from NSObject.
Then, when defining my URLSession, I set its delegate to 'self'.
Finally, I provide an authentication challenge handler.
In code, this all looks like:
public class SomeHTTPTask: NSObject, URLSessionDelegate {
public init() {
... initialize variables ...
super.init()
... now you are free to call methods on self ...
}
public func httpTask(withURL url: URL) {
let request = URLRequest(url: url)
... set up request ...
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: request) {data, response, error in
... now you have a result ...
}
}
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let user = Credentials.sharedInstance.userId, let password = Credentials.sharedInstance.password else {
completionHandler(.performDefaultHandling, nil)
return
}
let userCredential = URLCredential(user: user,
password: password,
persistence: .permanent)
completionHandler(.useCredential, userCredential)
}
}
Hopefully, the bits and pieces are self-explanatory. It's just an authentication challenge handler that provides credentials, if it can. The underlying URLSession will deal with the details, wither it's an NTLM or Basic auth, those sorts of things.
In the end, this seems a solid solution. At least, it worked for me.
Here's a nice reference document from Apple if you like reading that kind of thing.

what am I doing wrong Swift 3 http request and response

I'm having big problems in a project I'm currently working with.
I've been reading about URLSession various places but all of them seem to be outdated and refers to NSURLSession I thought that they would be fairly similar and they probably are but for a newbie like me I'm lost. what I do is not working and I do not like solutions I find because they all do their work in a controller..
http://swiftdeveloperblog.com/image-upload-with-progress-bar-example-in-swift/
this one for instance. I'm using the PHP script but wanted to make a networking layer I could invoke and use at will. but I'm lacking a good resource from where I could learn about how to use this api.
every place I find is similar to the link above or older. the few newer seem to also follow the pattern without really explaining how to use this api.
at the same time I'm new to the delegate pattern in fact I only know that it is something that is heavily used in this Api but I have no IDEA how or why.
Basically I need help finding my way to solve this problem here:
I've tried to do something like this:
public class NetworkPostRequestor:NSObject,NetworkPostRequestingProtocol,URLSessionTaskDelegate,URLSessionDataDelegate
{
public var _response:HTTPURLResponse
public override init()
{
_response = HTTPURLResponse()
}
public func post(data: Data, url: URL)
{
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration,delegate: self, delegateQueue: OperationQueue.main)
let task = session.uploadTask(with: request, from: data)
task.resume()
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void)
{
_response = response as! HTTPURLResponse
}
}
however I never even hit the PHPserver. the server when hit will say something like this in the terminal:
[Tue Mar 7 11:43:20 2017] 192.168.250.100:64265 [200]: /
[Tue Mar 7 11:43:20 2017] 192.168.250.100:64266 [404]: /favicon.ico - No such file or directory
Well that is when I hit it with my browser and there is no image with it. but alt least I know that it will write something with the terminal if it hits it. Nothing happens And without a resource to teach me this api I'm afraid I will never learn how to fix this or even if I'm doing something completely wrong.
I'm using Swift 3 and Xcode 8.2.1
Edit:
I've added this method to the class and found that I hit it every single time.
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
{
_error = error.debugDescription
}
the debug description have this string "some"
I never used this exact procedure with tasks but rather use the methods with callback. I am not sure if in the background there should be much of a difference though.
So to generate the session (seems pretty close to your):
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
Then I generate the request which stupidly enough needs an URL in the constructor:
var request = URLRequest(url: URL(string: "www.nil.com")!) // can't initialize without url
request.url = nil
Adding url with query parameters (you can just set the URL in your case, I have a tool to handle a few cases):
fileprivate func injectQueryParameters(request: inout URLRequest) {
if let query = queryParameters.urlEncodedString {
let toReturn = endpoint.url + "?" + query
if let url = URL(string: toReturn) {
request.url = url
} else {
print("Cannot prepare url: \(toReturn)")
}
} else {
let toReturn = endpoint.url
if let url = URL(string: toReturn) {
request.url = url
} else {
print("Cannot prepare url: \(toReturn)")
}
}
}
Then the form parameters. We mostly use JSON but anything goes here:
fileprivate func injectFormParameters( request: inout URLRequest) {
if let data = rawFormData {
request.httpBody = data
} else if let data = formParameters.urlEncodedString?.data(using: .utf8) {
request.httpBody = data
}
}
And the headers:
fileprivate func injectHeaders(request: inout URLRequest) {
headers._parameters.forEach { (key, value) in
if let stringValue = value as? String {
request.setValue(stringValue, forHTTPHeaderField: key)
}
}
}
So in the end the whole call looks something like:
class func performRequest(request: URLRequest, callback: (([String: Any]?, NSError?) -> Void)?) {
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request) { data, response, error in
// Response is sent here
if let data = data {
callback?((try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as [String: Any]?, error)
} else {
callback?(nil, error)
}
}
task.resume()
}
I hope this puts you on the right track. In general you do have a few open source libraries you might be interested in. Alamofire is probably still used in most cases.

Oauth1 iOS get data from API

How to get data from API with Oauth1? I just tried like this but it did not work.
import UIKit
import OAuthSwift
class TestLogin: UIViewController {
var oauthswift: OAuthSwift?
final let urlString = "https://conversation.8villages.com/1.0/contents/articles"
override func viewDidLoad() {
super.viewDidLoad()
self.doOAuth()
}
func doOAuth()
{
let oauthswift = OAuth1Swift(
consumerKey: "******",
consumerSecret: "******",
requestTokenUrl: "https://oauth.8villages.com/tokens/request-token",
authorizeUrl: "https://accounts.8villages.com/oauth/request-token",
accessTokenUrl: "https://accounts.8villages.com/oauth/access-token"
)
oauthswift.authorize(
withCallbackURL: URL(string: "https://8villages.com")!,
success: { credential, response, parameters in
print(credential.oauthToken)
print(credential.oauthTokenSecret)
print(parameters["userId"])
},
failure: { error in
print(error.localizedDescription)
}
)
}
func getHandleURL () {
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: { (data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
print(jsonObj!.value(forKey: "data"))
}
}).resume()
}
}
so, how must I do or I need a reference example get data from API with Oauth1? I just don't know how to start to build project with OAuth because I search in google, only tutorial OAuth for login with social media.
In order to send oAuth 1.0 request basically you need to calculate proper query string and body parameter which actually based on your server implementation.
You need to get following query param:
oauth_consumer_key
oauth_nonce
oauth_signature_method
oauth_timestamp
oauth_version
You can check this blog where all the params are explained in very good detail and also the signature process. Also this answer guide you how to create HMAC-SHA1 signature in iOS
In the end of this process you need to create signature based on signature method which your app and server both agreed upon.
Then a sample POST request should look like following: Which is taken from oAuth1 guide
POST /wp-json/wp/v2/posts
Host: example.com
Authorization: OAuth
oauth_consumer_key="key"
oauth_token="token"
oauth_signature_method="HMAC-SHA1"
oauth_timestamp="123456789",
oauth_nonce="nonce",
oauth_signature="..."
{
"title": "Hello World!"
}
Hope it helps.

NSMutableUrlRequest not sending proper parameters for HTTP request

I am trying to send a POST request so that a user can login to this app. However, when I try to send the information, the server returns an error message saying that it did not receive the login information. I have used this exact same code before but with the url having HTTPS instead of HTTP. Does swift 2 have a different method that deals with HTTP requests?
In my info.plist file I have added the following:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
The api calls work fine on every device except iOS, and the code works fine with a different url. If Swift 2 no longer accepts HTTP requests is there a work around?
static let URL = "http://url.com:3000"
static let netSession = NSURLSession.sharedSession() // A shared NSURLSession that will be used to make API calls
// Call to login with the provided credentials. If login is successful the handler function will
// receive 'true', otherwise 'false'.
static func login(email : String, password : String, handler : (success: Bool, error: APIError?) -> ()) {
let request = NSMutableURLRequest(URL: NSURL(string: "\(URL)/users/login")!)
request.HTTPMethod = "POST"
let params = ["email":email,"password":password]
request.HTTPBody = try? NSJSONSerialization.dataWithJSONObject(params, options: [])
netSession.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error != nil {
handler(success: false, error: APIErrorNetwork)
return
}
let jsonResponse = JSON(data: data!)
let httpResponse = response as! NSHTTPURLResponse
if httpResponse.statusCode == 200 {
// Handle the expected response
} else {
handler(success: false, error: APIError(json: jsonResponse))
print(httpResponse.statusCode);
}
}).resume()
}
Are you sure your server accepts JSON? Does it expect you to post form data instead?
If it does expect JSON, try to add a Content-Type header to your request:
request.setValue("application/json", forHTTPHeaderField: "Accept");
Some servers are picky.

Can any one tell me why i am getting Bad authentication error while executing this code(Swift)?

I am using Fabric SDK to add the twitter login button in my app.......
i add the authentication header in my URL but still it is showing Bad authentication error while executing.
Suggest me how to add Header in the URL in Swift.
let twitter = Twitter.sharedInstance()
let oauthSigning = TWTROAuthSigning(authConfig:twitter.authConfig, authSession:twitter.session())
let authHeaders = oauthSigning.OAuthEchoHeadersToVerifyCredentials()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.twitter.com/1.1/search/tweets.json?q=Himan_dhawan")!)
request.allHTTPHeaderFields = authHeaders
println(request)
var session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if((error) != nil) {
println(error.localizedDescription)
}
var strData = NSString(data: data, encoding: NSASCIIStringEncoding)
println(strData)
})
task.resume()
It's to do with the way that you're setting the headers on the request.
The Fabric doc's don't quite give you the full picture about creating the OAuth signing headers when wanting to use your own NSMutableURLRequest.
let authHeaders = oauthSigning.OAuthEchoHeadersToVerifyCredentials()
The return [NSObject : AnyObject]! dictionary gives you the values you need for the request. However, what it provides for the headers are different to what needs to be sent with the NSMutableURLRequest.
This is how you should be setting the headers for this request:
let twitter = Twitter.sharedInstance()
let oauthSigning = TWTROAuthSigning(authConfig:twitter.authConfig, authSession:twitter.session())
let authHeaders = oauthSigning.OAuthEchoHeadersToVerifyCredentials()
let mutableUrlWithUsableUrlAddress = NSMutableURLRequest(URL: usableUrlForRequest)
mutableUrlWithUsableUrlAddress.addValue(authHeaders[TWTROAuthEchoAuthorizationHeaderKey] as? String, forHTTPHeaderField: "Authorization")
This sets the required Authorisation Key as a value for the "Authorization" header on the request, opposed to when you pass in the authHeaders dictionary, it gets set for "X-Verify-Credentials-Authorization".
The Fabric doc's do go into this, but it's slightly more tucked away than it should be.

Resources