Authenticating iPhone application with Rails application - iphone

I have been doing research on this for quite some time and have not been able to solve my issue so I figure i'd ask. I have searched the other questions here on stackoverflow and other articles and they don't seem to get me in the right direction.
Here is what I am trying to do. I have a Rails 3 application that is only providing data in JSON format. The data will be entered by only 1 user so access to it will be very limited. I am using Devise so that it is protected via authentication.
I also have an iPhone application that will access this data. Since the Rails 3 application has username/password protection the iPhone application needs a way to authenticate with the application.
I have looked in to token authentication in Devise, but can't seem to get it to work. I have a loading symbol and it just spins and doesn't return any data. I also looked at http basic authentication. Again, haven't had any luck. On the iPhone side I am using ASIHTTPRequest. Following is what I am using for posting via the authenticity token:
//the url variable below is defined in my code but I did not paste that part
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"auth token" forKey:#"authenticity_token"];
[request addRequestHeader:#"Content-Type" value:#"application/json"];
[request setDelegate:self];
[request startAsynchronous];
When I tried http basic authentication I passed a username and password and that didn't work as well with the same issue and not returning data. I am sure I am missing something, but I can't find the information I need to get this working. I took a look at RestKit, but it seems to have a lot more then I need.
I only need the iPhone application to access the JSON data with it being protected on the Rails 3 side. The iPhone user will not need to sign in via a sign in form or be able to create, update or delete any data. It is strictly read only. Can anyone push me in the right direction?

The easiest way is to using basic auth
In you api/application controller on the rails side add
before_filter :check_auth
def check_auth
authenticate_or_request_with_http_basic do |username,password|
resource = User.find_by_email(username)
if resource.valid_password?(password)
sign_in :user, resource
end
end
end
On the iOS side you should set the Authorization header on the request object:
ASIHTTP supports this out of box. Luck you.
[request setUsername:#"username"];
[request setPassword:#"password"];
or you can add them to the URI of your request.
NSURL *url = [NSURL URLWithString:#"http://username:password#allseeing-i.com/top_secret/"];
this will allow a sign in with basic auth and further controllers will get your user from the session
then on the ios side you can just pass credentials like you would with any basic auth system.
I recommend using SSL/HTTPS in production or credentials will be passed in plain text or base64.

Related

How to initiate webservice request that expects JSON payload

I am JSON newbie and can't find any material on how to simulate JSON payload request.
My ultimate goal is to be able to build an objective-c iOS app that will handle these request-response. I am aware of ASIHttprequest framework and the request-response mechanism it works around.
However right now I have a webservice api which expects various json payloads and also provides response in json format. Here is an example:
Example URL:
https://mywebServiceURL/api/ApiKey/user/create
The ContentType header = “application/json”.
Payload: The PUT payload is a JSON dictionary, containing the following keyvalue pairs:
email
screenName
User’s screen name.
password
passwordConfirm
phoneNumber (optional)
User’s phone number (optional)
picture A png file (64x64), encoded as a Base64 string (optional)
Now my questions:
1 - how do I simulate this normally (outside ios, just for the sake of testing)? I searched google but can't find exactly what I need, I got curl.exe but it gives me same as what a browser gives, like method not allowed etc. But that's not the only thing I want. I want to play with various requests, supply values and take the api for a ride for sometime before I know how it really works for PUT, GET, POST etc.
2 - what is the best way to incorporate such stuff into iOS? I already have ASIHttp for web requests and JSONKit for JSON handling included in my project.
I have done this kind of stuff but with xml responses, get requests. JSON I am working for the first time. Just a pointer to an example stuff like this would be a great help.
There are several Chrome extensions, such as Advanced REST client or REST Console, that you can use to simulate REST calls. These are very quick and easy to install into your browser and will allow you to construct requests and view the response.
I recommend using the open source iOS networking library AFNetworking. This library has built in support for REST calls and JSON parsing. It is very easy to get up and running, and is very powerful if you need more advanced features.
Hope this helps.
Jsut to add upon Andy's suggestions as I have used similar kind of things in one of my recent app.
Used Chrome extension Poster for testing REST calls.
Used ASIHttpRequest for handling async queries.
Sample code snippet
//dictionaryData contains the login user details
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dictionaryData options:kNilOptions error:nil];
NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]autorelease];
//Handling ASI requests
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:uri];
[request addRequestHeader:#"Content-Type" value:#"application/json"];
[request addRequestHeader:#"Accept" value:#"application/json"];
[request appendPostData:[jsonData dataUsingEncoding:NSUTF8StringEncoding]];
[request setRequestMethod:#"POST"];
[ASIHTTPRequest setDefaultTimeOutSeconds:30];
request.delegate = self;
[request startAsynchronous];

Synchronous request or asynchronous when checking user's login data

In my application i need to implement verification if user has entered correct login and password or not. the login and the password are stored at the web server so i have to organize correct connection to the server. I'm an absolute beginner in everything about http requests and all that stuff. Actually i downloaded ASIHTTPRequest library and added it to my project just yesterday. My main problem is that i don't have an actual server by now (and i' m using just a conventional URL which later will be replaced with true server name but i want my code to be correct already)so i cannot test myself whether i'm doing things correctly or not.So my questions are:
1)What is the best way to organize verifying user's login and password? Should i use synchronous request or asynchronous? For all i know synchronous requests are rare in use cause they stop the application while the request is being performed but there's really nothing else needed to be done in this event so i'm a bit confused.What would you use?
2)I suppose verifying user's login and password by using http requests is pretty common task so there must be a general rule what kind of data the web server returns. I don't want to invent a wheel. should i use NSString returned by responseString to check if user's login and password match? What does server returns usually in such cases? How should my code look like? Something like
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:someUrl];
[request startSynchronous];
NSString *response = [request responseString];
if ([response isEqualToString:#"login and password match"])
//user enters next screen
else
//user is notified about the error
or something else? What would you do?
3)This request is not only i need to implement. Later i'm going to connect to the same URL with a different request. So how does the server know what kind of request is currently being used?
I really need your advice. Great thanks in advance
I have tried to answer your question,
Q:1. Synchronous or Asynchronous request model for login?
-> As per apple's documentation
A synchronous load is built on top of the asynchronous loading code made
available by the class. The calling thread is blocked while the asynchronous
loading system performs the URL load on a thread spawned specifically for
this load request.
also,
NSURLConnection provides support for downloading the contents of an
NSURLRequest in a synchronous manner using the class method
sendSynchronousRequest:returningResponse:error:. Using this method is
not recommended, because it has severe limitations:
The client application blocks until the data has been completely
received, an error is encountered, or the request times out.
Minimal support is provided for requests that require authentication.
There is no means of modifying the default behavior of response
caching or accepting server redirects.
As you are unaware of server side implementation, which may involve:
1. Redirection and other mechanisms for fulfilling the request.
2. It may require some proxy authentication or other similar stuff.
Q:2. What does server returns usually in such cases?
In general, a web service is implemented at server-side which returns XML or JSON as repsonse which you have to parse and use.
example response may look like:
for XML:
<auth>
<statusCode>0</statusCode>
<statusMessage>Login Successful.</statusMessage>
</auth>
for JSON
{
"statusCode" = "0"
"statusMessage" = "Login Successful."
}
tags(for XML) and keys(for JSON) will depend upon you sever implementation.
3. How does the server know what kind of request is currently being used?
-> The URL which you will use for request will tell server, what you are looking for?
for example
http://www.example.com/mywebapp/getItem?id="1";
Thanks,
or
http://www.example.com/mywebapp/removeItem?id="1";
The bold path item represents services which you are calling.

How do I get Ruby on Rails to work with ASIHTTPRequest?

I am creating an iOS application with a Ruby on Rails backend. I've got my logic setup and working in Rails, and have verified by testing in a web browser. My Rails application will not respond properly to my iOS application, saying something about an authenticity token.
I have setup an authenticity token in application.rb with the following code (I did a server reboot after adding this):
protect_from_forgery :secret => 'some_secret_here'
I'm passing the authenticity token from iOS to Rails with ASIHTTPRequest using the following code:
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"some_secret_here" forKey:#"authenticity_token"];
[request setPostValue:#"some value" forKey:#"some_parameter"];
[request setDelegate:self];
[request startAsynchronous];
Is there anything I'm missing or doing wrong?
This same code works fine with allow_forgery_protection set to false, so I assume the error lies in how I'm trying to pass the authenticity token.
The authenticity token is different for each page/request, so you need to find a way to send it through the pipe some other way.
Maybe try sending it via headers, like so:
class ApplicationController < ActionController::Base
before_filter :send_form_authenticity_token
private
def send_form_authenticity_token
response.headers['X-Authenticity-Token'] = form_authenticity_token
end
end
On the response callback of your first (and following) request(s), you need to do:
NSString *authenticityToken = [[request responseHeaders] objectForKey:#"X-Authenticity-Token"];
This implies that the first request you do is a GET one.
Would be better to have a global state variable, but this is the basic concept.
By the way, and just so you know, you don't need forgery protection if your rails application is just a backend sorts of app.
Forgery protection is there to avoid XSS attacks, and that wouldn't happen on the iPhone.

iOS: How to make a secure HTTPS connection to pass credentials?

I am creating my first iPad app. I have a web application that I would like to authenticate against and pull data from in a RESTful way.
If you open up the URL in the browser (https://myapp.com/auth/login), you will get a form to enter your username and password. I was thinking I could set the login credentials in the post data of the request and submit the data.
The site uses HTTPS for login so that credentials aren't passed in plain text over the internet.
How can I make a secure HTTPS connection to pass credentials? Will this remember that I am logged in for future requests? What is the best way to do this?
Further update, October 2013
Although at the time I wrote this answer, ASIHTTPRequest was maintained a widely supported, this is no longer the case. It is not recommended for new projects - instead use NSURLConnection directly, or use AFNetworking.
With AFNetworking, there is a [httpClient setAuthorizationHeaderWithUsername:username password:password]; method for http authentication, and create a POST style form is equally easy - see AFNetworking Post Request for that.
Original answer:
A lot of people use the ASIHTTPRequest class to deal with http & https on the iPhone/iPad, as it has a lot of useful features that are difficult or time consuming to achieve with the built in classes:
http://allseeing-i.com/ASIHTTPRequest/
Starting at the simplest level you'd start with something like:
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
NSLog(#"response = %#", response);
}
If you're using HTTP authentication, ASIHTTPRequest will automatically prompt the user for the username and password.
IF you're using some other form of authentication you probably need to request the username and password from the user yourself, and submit them as a POST value or a custom http header, and then the response may either include a token in a JSON or XML response, or it could set a cookie.
If you add more details as to how the authentication scheme works I can be a bit more specific.
Update
Based on the update, to emulate a POST form you'd just need to add lines like:
[request addPostValue:usernameString forKey:#"username"];
[request addPostValue:passwordString forKey:#"password"];
You will also need to change the way you create the request, instead of:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
do:
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
(I also forget to mention it above, there's code I put earlier is using a synchronous request, so you'd want to replace it with an asynchronous one to avoid blocking the UI once you'd proved it was working.)
There's a JSON framework for the iphone here:
http://code.google.com/p/json-framework/
which works well for me and is simple to use with ASIHTTPRequest.

ASIFormDataRequest- POST of file continues seems to continue after authentication fails

I'm posting some data to a http authenticated url with ASIFormDataRequest.
When the authentication fails and the authentication dialog delegate is invoked the upload progress seems to still proceed fully.
So in these cases:
1) The user's credentials are not yet stored in the keychain
2) The user's credentials which are stored in the keychain fail authentication (expired etc.)
I see this behavior:
I see the request come in to my
server and the 401 denied error
returned to the client
The uploadFailed delegate is not
called.
Progress bar delegate slowly fills as
the file appears to still be pushed
out on the network connection. It
completes in a time consistent with
the amount of time to fully upload
The built in authentication dialog
modal appears
User enters correct credentials
Progress bar delegate resets
Upload begins again - progress bar
fills as post data is received on
server
Finished delegate method is called as
expected.
Everything has uploaded just fine
with this second attempt
Here's where I setup my operation:
[self setRequest:[ASIFormDataRequest requestWithURL:uploadURL]];
[request setDelegate:self];
[request setDidFailSelector:#selector(uploadFailed:)];
[request setDidFinishSelector:#selector(uploadFinished:)];
[request setUseKeychainPersistence:TRUE];
[request setShouldPresentAuthenticationDialog:TRUE];
[request setShouldPresentCredentialsBeforeChallenge:TRUE];
[request setPostValue:captionTextField.text forKey:#"caption"];
[request setPostValue:[siteID stringValue] forKey:#"site_id"];
[request setFile:fileToUpload forKey:#"site_photo"];
[request setUploadProgressDelegate:progressView];
[request startAsynchronous];
I am thinking I need to issue a [request cancel] upon the authentication failing but I'm not sure where I should be doing this.
Is it expected behavior that the POST will still chug away even after the server has returned a 401?
Appreciate any guidance or pointers to existing questions that address this.
A 401 "error" is an HTTP status code, not a request failure. Your request went through okay and you got a response, which happens to be an authentication error notice. You are responsible for handling the response, whatever it might be.
There are many possible status codes you can get from a successful request, other than 401. As an aside, you may want to think about how to handle those kinds of responses as well, depending on what the end user is doing and what responses are appropriate.
The method -uploadFinished: should not generally be waiting until the data is fully uploaded before you see any NSLog statements or other notification of the request finishing.
So one thing to do is change the -uploadFailed: and -uploadFinished: method names to -requestFailed: and -requestFinished: to more accurately reflect what is happening in the logic of your application.
In your delegate's -requestFinished: method, check the responseStatusCode property of the request and issue the appropriate commands:
- (void) requestFinished:(ASIHTTPRequest *)request {
if ([request responseStatusCode] == 401) {
//
// cancel the current request and/or trigger a new,
// separate authentication request, passing along a
// reference to the request's body data, etc.
//
}
}
This is fairly common behaviour for HTTP clients - they do not attempt to read the reply from the server till they have fully sent the request, including the attached file.
It's common behaviour for a client to pre-emptively send the authentication if it has already has a request from the same server rejected with a 401 with in the same session - I am unsure if ASIHTTPRequest does this, but if it does one solution would be to make a GET request to the server before you do the POST. If the GET is successfully authenticated then the cached credentials should be sent for the post and hence there won't be a 401 error.
The only other option I can think of would be to move to cookie based authentication instead, if you are in control of the server, or use authentication in a custom http header. But I think my suggestion of doing a GET request first may be the best approach.