NSURLRequest with HTTP Authentication - ios

I am trying to authenticate to a HTTP RESTful site using NSURLRequest from an iPhone app. I put the user/pass encoded in base64 and pass it in as the "Authorization" header in the NSURLRequest with the word "Basic " appended to it. However, I keep getting an auth error in the other end. Here is my code. If I do a manual CURL request, it works. I also checked to make sure the Base64 is properly encoded and user, pass, and url are correct.
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
NSString *basicAuthCredentials = [NSString stringWithFormat:#"%#:%#", user, pass];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", AFBase64EncodedStringFromString(basicAuthCredentials)];
authValue = [authValue stringByReplacingOccurrencesOfString:#"\n" withString:#""];
[urlRequest setValue:authValue forHTTPHeaderField:#"Authorization"];
[urlRequest setHTTPMethod: #"GET"];
NSData *httpData = [self encodeDictionary:data];
[urlRequest setHTTPBody:httpData];
NSHTTPURLResponse *response;
NSError *error;
NSData* result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];

The auth code looks fine. Sure, the stringByReplacingOccurrencesOfString is unnecessary, but doesn't hurt. And I might use the native base64 methods (NSData methods base64EncodedDataWithOptions or base64EncodedStringWithOptions in iOS 7, base64Encoding in iOS versions prior to 7) rather than AFNetworking's, but I don't think this is the problem either.
Assuming your userid and password are correct and that your server really is using basic authentication (which I assume is correct if your cURL is succeeding), the only thing that is obviously incorrect here is the call to setHTTPBody for a GET request. NSURLConnection will not send the body for a GET request (nor should it, as the body is semantically meaningless in GET requests). If you want to send a request with a body, you should generally use POST. Or if you want to send parameters in a GET request, you can add them to the URL, but not to the body.

Related

How to connect (GET request) ios app to django rest framework

I came to the last stage of development of my app and here is the bit that I've never done before.
My friend has developed and API for my app to send and receive data, using django rest framework.
I need to authenticate my app to connect to it, send some data and receive data.
What I have found so far is:
NSURL *url = [NSURL URLWithString:#"http://localhost:8080/my/path/to/api/login/"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", #"myUsername", #"myPassword"];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", authData];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
//------------------------------------------------------------------------------------------------------------------------------------//
//EDIT: Added this based on answer form #Quver.
NSURLResponse *response1;
NSError *responseError;
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response1 error:&responseError];
if (result.length > 0 && responseError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:result
options:0
error:NULL];
NSLog(#"Got response form server: %#", greeting);
}
This equals output like:
<0a0a3c68 746d6c3e 0a0a2020 20203c68 6561643e 0a202020 20202020 200a2020 20202020 20200a20 20202020 + ~50 lines of similar stuff. Hope this helps.
//-----------------------------------------------------------------------------------------------------------------------------------------//
I guess this is the way to create request. What should I do next? And how do I know that I have connected?
Then, if I have connected, how do I get data form there? (I have a url that gives me json as output - this is what I want to get). Assume the url to be http://localhost:8080/url/that/gives/json/.
Thank you for any help. Hope this is enough information for the question. I will add anything else required.
NSURLResponse *response;
NSError *responseError;
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&responseError];
Add this to get response. You already prepared request, now it's time to send it with NSURLConnection. I you sync request insted of async, becouse of using GCD for whole metod request + sqlite update.
After a few days of searching I have found a way.
First, we need a token for authentication. I am generating it through terminal for now:
curl -X POST -d "grant_type=password&username=<your username>&password=<your password>" http://<client secret>:<client id>#url/to/token/page
Then, in your view controller where you want to connect:
//always put </> at the end of link
NSURL *aUrl = [NSURL URLWithString: #"http://where/you/trying/to/conect"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aUrl
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0];
[request addValue:[NSString stringWithFormat:#"<type> <your token>"] forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
NSError *error = nil;
self.response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error: &error];
This will return any json that the page supposed to return. This might not be the most secure way, but this is exactly what I need for now. I will look at more secure solutions like OAuth 2, later.
Hope this helps to someone.

NSURLConnection Authentication Issue

I have a bit of code for sending/receiving data from a webpage that works ~90% of the time. However the ~10% of the time it doesn't really bothers me, and I can't understand what's going on because the code isn't changing, and I have a similar android implementation that works flawlessly. Here's what I have.
- (NSString*) postData:(NSString*) url params:(NSString*) params
{
NSURL* urlRequest = [NSURL URLWithString:url];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[urlRequest standardizedURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSError* err = nil;
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&err];
if (!err) {
return [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];;
}
return nil;
}
- (NSString*) getData:(NSString*) url
{
NSURL* urlRequest = [NSURL URLWithString:url];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[urlRequest standardizedURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSError* err = nil;
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&err];
if (!err) {
return [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];;
}
return nil;
}
I call getData to make the initial connection to the website. It has some information I parse out (two security tokens to send in when I send postData) and then send the data through the POST request. This first part always works everytime. However I then send another POST request to change pages again (after parsing out any new security tokens), and this is the part that fails ~10% of the time. By fail, I mean the website responds that my session has been compromised. I know this message happens if the security tokens are invalid, but I've checked those over and over and over and everytime they're fine. My theory is that I'm loosing cookie information, but from what I understand NSURLConnection should store the cookie information based on domain.
If I open a browser and go to the page, then delete the only cookie that is stored however, it takes me back to the login page (like expected) and doesn't give me the error message about my session being corrupted.
Any ideas would be greatly appreciated

Get a JSON response from JIRA webservice in iOS

I am attempting to access the JIRA REST webservice using iOS. I have managed to get the requests to work, but the response is not JSON. I get a response with a session id and everything from the login request that comes before this one, and this request returns a similar response, but the data coming back that is suppose to be JSON but looks like : <61737369 616e2e6e 65742f73 65637572 652f7072 (continues for several lines)>. I have made the same requests with JMeter and it gets returned a JSON string, but this request in iOS does not.
NSURL *url = [NSURL URLWithString:#"https://company.atlassian.net/rest/
api/2/project"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url2];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type" ];
[request setHTTPMethod:#"GET"];
//Encoded authorization header
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
[NSURLConnection sendAsynchronousRequest:request queue:
[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse
*response, NSData *data, NSError *error){ }];
I have attempted to manipulate the Content-Type trying random different things, but none have worked. Any ideas on how to get it to return JSON?
You're setting the Content-Type header, but you don't appear to be sending any content. It seems more like you should be setting the Accept header.
You can't log an NSData instance and expect to see the contents, even if the content is a string. When you log, either try to convert the data into a string (alloc, initWithData:) or deserialize the JSON (JSONObjectWithData:options:error:).

iOS: Two requests from one call?

I'm writing an iPad app that needs to communicate with a backend server. The first order of business in using this backend is to login, and for this the server has a URL that we can POST to, which I do like this:
// Create the request.
NSString* loginURL = #"http://foo.local/signature/service/auth/rest/firewall/login";
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:loginURL]];
NSString* credentials = #"{\"userName\":\"foo2#foolinator.com\", \"password\":\"password\"}";
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[credentials dataUsingEncoding:NSASCIIStringEncoding
allowLossyConversion:YES]];
// Logging in...
NSError* error = nil;
NSURLResponse* response;
NSData* result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
NSString* responseString = [NSHTTPURLResponse localizedStringForStatusCode:[httpResponse statusCode]];
NSLog(#"Response String is: %#\n", responseString);
NSLog(#"Header fields are: %#\n", [httpResponse allHeaderFields]);
What's odd is that the response I'm getting is Error 405: Method Not Allowed. I would've expected this if I was doing a GET, but I'm doing a POST.
I installed WireShark to examine the HTTP requests and it seems that there's actually two being made. The first one, is a POST call, and the server returns some cookie information as a response, and then a second GET call, which is what the code above gets back.
Why does this happen? Is it something to do with the response from the server the first time?
While you research your web service's login API, a couple of unrelated observations:
You should be sending this asynchronously if doing this from the main queue. Never issue synchronous network requests from the main queue. If you do this synchronously on the main queue (a) you risk having the iOS watch-dog process kill your app, which happens if the main queue becomes unresponsive while some synchronous network request is being processed; and (b) it's a bad UX to simply freeze an app during a network request ... if you need, disable the UI and show an indeterminate progress indicator (a UIActivityIndicatorView) while the network request is in progress.
You should probably be setting a value forHTTPHeaderField for Content-Length. It's probably not required, but it's good practice.
You probably should not be using a string with the JSON with the userid and password, but rather you should probably build this from a NSDictionary using something like NSJSONSerialization. As it is, if your password, for example, had any characters that needed to be escaped (e.g. a quotation mark), the existing code might not work. Using NSJSONSerialization is an easy way to ensure that your JSON is properly formatted.
You probably should not be sending a password in plaintext in your JSON request. At the very least, I hope your server employs HTTPS.
Anyway, with these observations, assuming your server really is expecting a JSON request, I might suggest something like:
// hopefully your production server is employing HTTPS
NSString *loginURL = #"https://foo.local/signature/service/auth/rest/firewall/login";
// use NSJSONSerialization to create JSON rather than building it in a NSString
NSDictionary *postDictionary = #{#"userName": userName, #"password": password}; // assuming you have NSString variables, `userName` and `password`
NSError *error = nil;
NSData *postData = [NSJSONSerialization dataWithJSONObject:postDictionary options:0 error:&error];
NSAssert(postData, #"dataWithJSONObject failed: %#", error);
// when creating request, also set Content-Length
NSURL *url = [NSURL URLWithString:loginURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
[request setValue:#"application/json; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
// issue request asynchronously
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(#"sendAsynchronousRequest error: %#", connectionError);
return;
}
// process the server response here
}];
You might still want to use a NSURLConnectionDataDelegate/NSURLConnectionDelegate based request (you can identify redirects, challenges, cancel it if you need, etc.), but the above might be a good start at an asynchronous JSON-based request.

Objective C: http POST request getting Unescaped

I am using a NSMutableURLRequest to send a (synchronous) POST request to a web service.
The request body contains a combination of XML and JSON. Something like :
<element>
<element 2>
<![CDATA[
{"jsondata1":{"datafield1":"data1"},
"jsondata2":"some data"
}
]]>
</element 2>
</element>
the JSON string has been escaped (Using escape() in Javascript)
When I receive the request at the other end I notice that all escaped characters have been unescaped!!
Can anyone tell me why this is happening? And how can I prevent this?
Here is the code that I'm using to send the request:
NSURL *url = [NSURL URLWithString:#"https://my-webservice.com/something.do"];
NSData *xmlResponseDataSave;
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d",[xmlRequestString length]];
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
NSData *saveData=[xmlRequestString dataUsingEncoding:NSUTF8StringEncoding];
[req setHTTPBody:saveData];
NSError *error=nil;
NSURLResponse *resp=[[NSURLResponse alloc] init];
xmlResponseData=[NSURLConnection sendSynchronousRequest:req returningResponse:&resp error:&error];
NSString *xmlResponseString=[[NSString alloc] initWithData:xmlResponseData encoding:NSUTF8StringEncoding];
Please note: I cannot make any change to the xml or the web service. I can only change the objective c code.
After a lot of trial and error the only solution that I could come up with is to string used in the request body before sending it (Since it's gonna get unescaped while being sent):
xmlRequestString=[xmlRequestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *saveData=[xmlRequestString dataUsingEncoding:NSUTF8StringEncoding];
Though this is not a fix in the truest sense, just a work around. But it works for me (So far).
I'm still clue less about the root cause of the problem and I am still looking for a more elegant solution.

Resources