How to replicate POST login with NSURLSession - ios

I'm trying to write a iOS app to be my personal front-end to my city library's website. In effect, I want my app to invisibly visit the library's webpage, enter my username and password, and then send HTTP requests to invisibly browse through the library's web interface to get my list of checked-out items, renew items near their due dates, etc.
The first step is to be able to send in my username and password. I've stripped down the library's login page to the following:
<html>
<body>
<form action="http://brown.ent.sirsi.net/client/en_US/home/search/patronlogin.loginpageform/TRAINING" method="post">
<input name="t:ac" type="hidden" value="http:$002f$002fbrown.ent.sirsi.net"></input>
<input name="t:formdata" type="hidden" value="gFJQUIXDSUXVXoGv49wm3q8cIUQ=:H4sIAAAAAAAAAFvzloG1XJ5Btjg1sSg5Qz8gsaQoP88nPz0zzyorvrQ4tSgvMTe1uIjBNL8oXS+xIDE5I1WvJLEgtbikqNJULzm/KDUnM0kvKbE4Vc8xCSiYmFzilpmak6ISnFpSWqAaepj7oejxP0wMjD4M3Mn5eUDTc/yAJpYwCPlkJZYl6uck5qXrB5cUZealW1cUlDBwIWwlxlmOpDoroCg/ObW4OLg0KTezuDgzP+/wuhSTtG/zzjExMFQU4LSyILG4uDy/KKW4kKGOgQHsTJgQQT0gLazl0gySWFRlZKakpOYBPeKA1yPJ+bkF+XmpeSXFeh5gHZj+iGpXEK6Q3sGGEdQg6xlBQcsGsQyHS4pBJpWQ4hKw3SWYLpkZ/Ely65YWZyYGJh8GjuScTKBqT3AogFyWmpOaCxRAcRkHxPJ4AyQmABiJtLaZAgAA"></input>
Library Card #: <input name="j_username" value = "29878001234567" type="text"></input><br/>
PIN: <input name="j_password" value="1234" type="text"></input><br/>
<input name="hidden" value="SYMWS" type="hidden"></input>
<input type="submit"></input>
</form>
</body>
</html>
When I open this HTML file from my hard drive, enter my real library card number and PIN, and submit the form, I successfully log into the library's system. When I click on "submit" with just the dummy numbers in the code above, I receive the "login failed" screen just as I should.
I've tried to imitate this in my Swift code as follows:
func loginToLibrary() {
let request = NSMutableURLRequest(URL: NSURL(string: "http://brown.ent.sirsi.net/client/en_US/home/search/patronlogin.loginpageform/TRAINING")!)
request.HTTPMethod = "POST"
var postString = "t:ac=http:$002f$002fbrown.ent.sirsi.net"
postString += "&t:formdata=gFJQUIXDSUXVXoGv49wm3q8cIUQ=:H4sIAAAAAAAAAFvzloG1XJ5Btjg1sSg5Qz8gsaQoP88nPz0zzyorvrQ4tSgvMTe1uIjBNL8oXS+xIDE5I1WvJLEgtbikqNJULzm/KDUnM0kvKbE4Vc8xCSiYmFzilpmak6ISnFpSWqAaepj7oejxP0wMjD4M3Mn5eUDTc/yAJpYwCPlkJZYl6uck5qXrB5cUZealW1cUlDBwIWwlxlmOpDoroCg/ObW4OLg0KTezuDgzP+/wuhSTtG/zzjExMFQU4LSyILG4uDy/KKW4kKGOgQHsTJgQQT0gLazl0gySWFRlZKakpOYBPeKA1yPJ+bkF+XmpeSXFeh5gHZj+iGpXEK6Q3sGGEdQg6xlBQcsGsQyHS4pBJpWQ4hKw3SWYLpkZ/Ely65YWZyYGJh8GjuScTKBqT3AogFyWmpOaCxRAcRkHxPJ4AyQmABiJtLaZAgAA"
postString += "&j_username=29878001234567"
postString += "&j_password=1234"
postString += "&hidden=SYMWS"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard error == nil && data != nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("response to login attempt = \(responseString)")
}
task.resume()
}
When I run the Swift code, however, the following is printed to the console:
response to login attempt = Optional(<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><link rel="shortcut icon" href="/client/assets/4.5.03/ctx/favicon.ico" type="image/vnd.mircrosoft.icon" id="favicon"/><title>Unexpected Error</title><link type="text/css" rel="stylesheet" href="/client/assets/4.5.03/core/default.css"/><meta content="Apache Tapestry Framework (version 5.3.7)" name="generator"/></head><body><div id="exception"><h1>Unexpected Error</h1><div class="pageheading">The system encountered an error while processing the following request:</div><div class="url">/en_US/home/search/patronlogin.loginpageform/TRAINING</div><h2>Error Summary</h2><div class="cause">java.io.IOException: Client data associated with the current request appears to have been tampered with (the HMAC signature does not match).</div><div class="timestamp">Wed May 25 22:51:42 CDT 2016</div></div></body></html>)
Interpreting that in a browser, it is:
Unexpected ErrorUnexpected ErrorThe system encountered an error while processing the following request:/en_US/home/search/patronlogin.loginpageform/TRAININGError Summaryjava.io.IOException: Client data associated with the current request appears to have been tampered with (the HMAC signature does not match).Wed May 25 22:51:42 CDT 2016)
Can anyone please help me figure out how the POST request sent by my Swift code differs from the POST request my browser sends from the stripped-down webpage above?
For what it's worth, I can obtain the same error (HMAC signature not matching) in the browser from my stripped-down webpage if I change the value of the t:formdata input. I've tried percent-encoding the = (about 30 characters into the t:formdata value) as %3D, and even the /'s as %2F, but that didn't help.

The site you're trying to post to is using HMAC to authenticate.
Short Answer: You can't accomplish what you're trying. You need to do some research to work around HMAC.
Long Answer:
A HMAC(Hash-based Message Authentication Code) is the product of a hash function applied to the body of a message along with a secret key. So rather than sending the username and password with a Web service request, you send some identifier for the private key and an HMAC. When the server receives the request, it looks up the user’s private key and uses it to create an HMAC for the incoming request. If the HMAC submitted with the request matches the one calculated by the server, then the request is authenticated.
There are two big advantages. The first is that the HMAC allows you to verify the password (or private key) without requiring the user to embed it in the request, and the second is that the HMAC also verifies the basic integrity of the request. If an attacker manipulated the request in any way in transit, the signatures would not match and the request would not be authenticated. This is a huge win, especially if the Web service requests are not being made over a secure HTTP connection.
Look into this link for more information on HMAC.

URL encode is missing for the data you post to symphony, I have got the same issue you encountered. That is the reason why symphony got a wrong t:formdata

Related

How to handle '€' in a password in a HTTP post request

I have an iOS app which sends a HTTP request for the login to our Webserver. The login basically works fine, but as soon as someone got a '€' in his password the login fails.
This bug only happens in the app. We also have a web application, which sends the same login request to the same webserver and I can perfectly log in when I do that in my browser, even if there is a '€' in my password.
Here's the function that generates the request:
func SignOn() {
var request = Helper.getURLRequest(str: str, method: "POST")
guard let httpBody = try? JSONEncoder().encode(Helper.Logon.init(domain: String(userDomain[0]), user: String(userDomain[1]), p: ""))else { return }
request.httpBody = httpBody
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
urlSession.dataTask(with: request) { (data, response, error) in
do {
guard let data = data else { throw Helper.MyError.NoConnection }
Helper.isAuthenticated = try JSONDecoder().decode(Helper.Authentication.self, from: data)
task.leave()
} catch {
[...]
}
static func getURLRequest(str: String, method: String) -> URLRequest {
let url = URL(string: str)
var request = URLRequest(url: url!)
let loginString = "\(Helper.loggedOnUserWithDomain):\(Helper.loggedOnUserPassword)"
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData!.base64EncodedString()
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = method
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
return request
}
SignOn() gets called as soon as the user presses the "login" button in the app. Username and password are stored in two variables in my Helper class.
SignOn() will then call a function that generates the request - also in my Helper class.
I double checked every step in getURLRequest(). loginString and loginData both keep the € and they are perfectly displaying the character when I let Xcode print the variables.
I then checked the base64 string. Let's say someone enters "t€stpassword". The encoded base64 string should be VOKCrHN0cGFzc3dvcmQ=, which the function got right. I then let the function decode the base64 string again and checked if "t€stpassword" was the result, which again was true.
Then I checked the request with HTTP interception, but it also had the '€' in his body.
I already tried to percent escape the '€' character but that does also not work. The '€' gets percent escaped correctly, but I think the web server can't handle it then, I don't really know tbh. I used this method: how to http post special chars in swift
I'm out of ideas what I'm doing wrong here. I'm pretty new to Swift so I don't want to rule out, that I'm missing something obvious. Could the web server be the issue? But as I said, the login is working when doing it in a browser, so the server cannot be the issue, right?
According "The 'Basic' HTTP Authentication Scheme" in RFC 7617, section 3:
3. Internationalization Consideration
User-ids or passwords containing characters outside the US-ASCII
character repertoire will cause interoperability issues, unless both
communication partners agree on what character encoding scheme is to
be used. Servers can use the new 'charset' parameter (Section 2.1)
to indicate a preference of "UTF-8", increasing the probability that
clients will switch to that encoding.
Furthermore,
For the user-id, recipients MUST support all characters defined in
the "UsernameCasePreserved" profile defined in Section 3.3 of
RFC7613, with the exception of the colon (":") character.
For the password, recipients MUST support all characters defined in
the "OpaqueString" profile defined in Section 4.2 of RFC7613.
The "recipient" here is the backend. The referenced RFCs in the cited paragraphs clearly describe how the backend should process the Unicode characters and how to perform the comparison operator. You might test the server against the specification to figure out whether the server behaves correctly.
The client however, should at least check for a semicolen in either the password or user-id which would be an invalid credential for Basic HTTP Authentication.
So, your code should work, unless the backend does not want to handle Unicode. If this is the case, only allow ASCII on the client side.
When the authentication fails, a server might message the expected charset in the response in the Authenticate header:
WWW-Authenticate: Basic realm="foo", charset="UTF-8"
However, specifying a charset parameter is "purely advisory". We can't rely on the server sending this.
Basic HTTP is what the name suggests: a basic authentication scheme. It has been deprecated for a while now.
If possible, use a more secure and a more resilient authentication scheme.

API request times out ONLY if I pass parameters

I am trying to figure out why any time I pass valid json to my server for a GET request the connection times out. I do not have any problems passing json to my server in any other request type other than GET... I've tested the server-side code locally and the queries work as expected.
I want to fetch a specific user from my database and I need to pass in a username, so I send the username to the server.
The error I keep getting (If I don't send any paramaters to the server, and just return current_user, it works and I don't get this error)
Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x604000454c40 {Error Domain=kCFErrorDomainCFNetwork Code=-1001
Here is the part of the API call code where I set the request type and values to send to the server, in case:
let request = NSMutableURLRequest(url: url as URL)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
request.httpMethod = "GET"
Server set up, just in case it matters:
Digitalocean droplet, Linux, Ubuntu 16.04, Nginx
EDIT/ UPDATE
If I change the method from a GET to a POST (and keep the server code exactly the same), the server sends the correct data back immediately.
The server side code for this is very short, so I really don't see how it can be timing out due to optimization:
user = UserModel.find_by_username(data['username'])
if user:
return {"response": [user.json()]}, 200
return {"response": "user not found"}, 404
It really seems as if we can't send json via a GET method. It doesn't seem to work on both a simulator nor an actual device...I saw a similar SO post where someone commented exactly what I'm experiencing. Changing GET to POST was the fix....but it is a GET request, so why wouldn't this work?
GET-Method does not support http body. When you send your parameter as url encoded it will work.
Example:
http://www.example.de?username=abc

BigCommerce oAuth auth token request always returning 401

I can not figure out what I'm doing wrong. I'm developing an App for BigCommerce and can not get the simple oAuth exchange to work correctly.
The initial get request is being made to https://www.my-app.com/oauth/bigcommerce/auth. This is the code in the controller for that request. It's a Laravel 5.6 app:
use Illuminate\Http\Request;
use Bigcommerce\Api\Client as Bigcommerce;
class BigcommerceOAuthController extends Controller
{
public function auth(Request $request)
{
$object = new \stdClass();
$object->client_id = 'my-client-id';
$object->client_secret = 'my-client-secret';
$object->redirect_uri = 'https://my-app.com/oauth/bigcommerce/auth';
$object->code = $request->get('code');
$object->context = $request->get('context');
$object->scope = $request->get('scope');
$authTokenResponse = Bigcommerce::getAuthToken($object);
$storeHash = str_replace('stores/', '', $request->get('context'));
Bigcommerce::configure(array(
'client_id' => 'my-client-id',
'auth_token' => $authTokenResponse->access_token,
'store_hash' => $storeHash
));
echo "<pre>";
print_r($authTokenResponse);
print_r(Bigcommerce::getTime());
echo "</pre>";
}
}
Every time I try to install my draft app from the BigCommerce control panel, I get an error because $authTokenResponse is not an object. When I debug further into the Bigcommerce\Api\Connection class, I can see that the response from the server is empty, and the status is a 401, which means "Unauthorized".
I can't figure out why I am getting this error. As far as I can see, I'm doing everything right. I've tried urlencoding the string retrieved from $request->get('scope'), since that string becomes unencoded by Laravel, but that didn't seem to help.
I am also confused how this is even supposed to work at all. In the BigCommerce docs, they show this example POST request, which uses application/x-www-form-urlencoded Content-Type and passes the request body as a url encoded string:
POST /oauth2/token HTTP/1.1 Host: login.bigcommerce.com Content-Type:
application/x-www-form-urlencoded Content-Length: 186
client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&code=qr6h3thvbvag2ffq&scope=store_v2_orders&grant_type=authorization_code&redirect_uri=https://app.example.com/oauth&context=stores/{STORE_HASH}
However, if you inspect what's going on in the Connection class, you can see that the Content-Type is being set to application/x-www-form-urlencoded as the docs say, but the request body is being passed in as a json string, not a url string. Shouldn't the request be a url encoded string as the docs suggest?
A couple of things here to check:
Do you have a public URL where you can receive the Auth Callback?
If so, did the store owner registered the app successfully? https://developer.bigcommerce.com/api/registration
When you have the client_id and secret_id. You should have all of the details needed to send a POST request to the BC Auth Token Service at https://login.bigcommerce.com/oauth2/token
The content uses URL encode Make sure to URL encode your content. Be careful of of the encoding of & and = signs when those are actually being used as separators.
More details can be found in this post:
Can BigCommerce Private Apps use OAuth

Xcode iOS password get AF 'posted' with "some" hash / type of encryption - what kind?

I'm testing an app not yet published. I have a client side register/log-in, using Alamofire to post and then retrieve and parse JSON. Unless Alamofire has "blackboxed" some type of hash, I am not aware of having coded any kind of hash anywhere, yet.
When I went to look into why the test passwords (passed via SSL, HTTPS, and without any effort to encrypt, yet) were showing up on the server side looking like the result of a hash, I compared that result to a deliberate server side Sha256 hash (done on the raw, plain text password matching the original that got passed from the app). I am seeing this:
"ccc" ----> Sha256 hash = 64daa44ad493ff28a96effab6e77f1732a3d97d83241581b37dbd70a7a4900fe
"ccc" ----> "simple iOS post" (via Alamofire) = 9df62e693988eb4e1e1444ece0578579
As you can see, the values are very different, and this means unless I know what happened on the way over, I cannot authenticate anyone on the server side, nor can I use any server side password reset functions, because I have no idea what kind of hash was used.
Can anyone help me know what happened to hash the password?
Here's the simple Alamofire-based code doing the post (Xcode 9, Swift 4):
//creating parameters for the post request
let parameters: Parameters=[
"username":textFieldUserName.text!,
"password":textFieldPassword.text!,
"name":textFieldName.text!,
"email":textFieldEmail.text!,
"phone":textFieldPhone.text!,
"user_type":String(user_type),
"user_privileges":String(user_privileges)
]
print("Post Contents ('parameters') = \(parameters)")
//Sending http post request
Alamofire.request(URL_USER_REGISTER, method: .post, parameters: parameters).responseJSON
{
response in
//printing response
print(response)
//getting the json value from the server
if let result = response.result.value {
//converting it as NSDictionary
let jsonData = result as! NSDictionary
}
}
Well, I feel sheepish. I found a hash being applied in the PHP on the server side.
md5($pass);
No need to bother with this one. Now wish I had not even posted it. But, maybe it will help someone.

Why is one version of my http POST body not working?

I am trying to send something to an API using POST. The post body is made up of x 2 properties.
If I create the post body as one long string:
let postBody = "ministryId=nameOfMinistryHere&personId=1005" and then encode the string as follows urlRequest.httpBody = postBody.data(using: String.Encoding.utf8) it works perfectly.
But I am trying to create the post as a dictionary and then pass it to the API, but can't get it to work.
let postBody = ["ministryId":"nameOfMinistry", "personId":"1005"]
do {
try urlRequest.httpBody = JSONSerialization.data(withJSONObject: postBody, options: .prettyPrinted)
} catch {
print("problems serializing data")
}
When I use the latter option I am getting a 400 error from the server.
What am I missing?
Thanks in advance.
URLComponents is the class for dealing with multiple parameters. Code snippet:
let postBody = ["ministryId":"nameOfMinistry", "personId":"1005"]
let urlComponents = URLComponents(string: myURL)
let urlRequest = URLRequest(url: urlComponents.url!)
// transform the dictionary into queryItems
urlComponents.queryItems = postBody.map { URLQueryItem(name: $0, value: $1) }
urlRequest.httpBody = urlComponents.percentEncodedQuery?.data(using: String.Encoding.utf8)
thecloud_of_unKnowing answer to your comment as it was long i am posting it here -:
HTTP headers can be mainly classified into two types: HTTP Request Header Whenever you type a URL into the address bar and try to access it, your browser sends an HTTP request to the server. The HTTP request header contains information in a text-record form, which includes particulars such as the type, capabilities and version of the browser that generates the request, the operating system used by the client, the page that was requested, the various types of outputs accepted by the browser, and so on. HTTP Response Header Upon receiving the request header, the Web server will send an HTTP response header back to the client. An HTTP response header includes information in a text-record form that a Web server transmits back to the client's browser. The response header contains particulars such as the type, date and size of the file sent back by the server, as well as information regarding the server.SO you are just sending extra information to your server to let it know what kind of request it will accept.
Content-type: application/json; charset=utf-8 designates the content to be in JSON format, encoded in the UTF-8 character encoding. Designating the encoding is somewhat redundant for JSON, since the default (only?) encoding for JSON is UTF-8. So in this case the receiving server apparently is happy knowing that it's dealing with JSON and assumes that the encoding is UTF-8 by default, that's why it works with or without the header.
Simply make a dictionary as follows:
let jsonBody = ["username": email, "password": password]
Then you can do something like this:
let request = NSMutableURLRequest(url: NSURL(string: "YOUR URL") as URL)
request.httpBody = try! JSONSerialization.data(withJSONObject: jsonBody, options: .prettyPrinted)
Hope that helps!

Resources