iPhone - HTTPS connection to Server with Self-Signed Certificate [duplicate] - iphone

I have a cocoa class set up that I want to use to connect to a RESTful web service I'm building. I have decided to use HTTP Basic Authentication on my PHP backend like so…
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
//Stuff that users will see if they click 'Cancel'
exit;
}
else {
//Validation Code
echo "You entered info.";
}
?>
At this point I'm using a synchronous NSURLConnection, which I understand the Apple documentation states has less support for Authentication.
But is it even possible at all? I can do cookie authentication very easily sans NSURLProtectionSpaces or NSURLCredentials or any of the authentication classes. Also, are there any resources where I can read more about the Cocoa Authentication classes?
Thanks.
UPDATE: mikeabdullahuk
The code you supplied (the second example) is almost identical to what I had written. I have done some more investigating, and discovered that the NSURLConnection is returning an error…
Error Domain=NSURLErrorDomain Code=-1012 UserInfo=0x1a5170 "Operation could not be completed. (NSURLErrorDomain error -1012.)"
The code corresponds to NSURLErrorUserCancelledAuthentication. So apparently my code is not accessing the NSURLCredentialStorage and instead is canceling the authentication. Could this have anything to do with the PHP HTTP Authentication functions? I'm quite confused at this point.

A synchronous NSURLConnection will absolutely work with NSURLCredentialStorage. Here's how things usually work:
NSURLConnection requests the page from the server
The server replies with a 401 response
NSURLConnection looks to see what credentials it can glean from the URL
If the URL did not provide full credentials (username and password), NSURLConnection will also consult NSURLCredentialStorage to fill in the gaps
If full credentials have still not been determined, NSURLConnection will send the -connection:didReceiveAuthenticationChallenge: delegate method asking for credentials
If the NSURLConnection now finally has full credentials, it retries the original request including authorization data.
By using the synchronous connection method, you only lose out on step 5, the ability to provide custom authentication. So, you can either pre-provide authentication credentials in the URL, or place them in NSURLCredentialStorage before sending the request. e.g.
NSURLRequest *request =
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://user:pass#example.com"]];
[NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
or:
NSURLCredential *credential = [NSURLCredential credentialWithUser:#"user"
password:#"pass"
persistence:NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:#"example.com"
port:0
protocol:#"http"
realm:nil
authenticationMethod:nil];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
forProtectionSpace:protectionSpace];
[protectionSpace release];
NSURLRequest *request =
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://example.com"]];
[NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];

In a situation where a 401 or other authentication challenge is unacceptable/impossible, I sometimes use a dummy CFHTTPMessage to generate the authetication line, then copy that back into the NSURLRequest:
// assume NSString *username and *password exist and NSURLRequest *urlRequest
// exists and is fully configured except for HTTP Basic Authentication..
CFHTTPMessageRef dummyRequest =
CFHTTPMessageCreateRequest(
kCFAllocatorDefault,
CFSTR("GET"),
(CFURLRef)[urlRequest URL],
kCFHTTPVersion1_1);
CFHTTPMessageAddAuthentication(
dummyRequest,
nil,
(CFStringRef)username,
(CFStringRef)password,
kCFHTTPAuthenticationSchemeBasic,
FALSE);
authorizationString =
(NSString *)CFHTTPMessageCopyHeaderFieldValue(
dummyRequest,
CFSTR("Authorization"));
CFRelease(dummyRequest);
[urlRequest setValue:authorizationString forHTTPHeaderField:#"Authorization"];
This may seem completely a bizarre way to do it but it is tolerant of situations where the username/password aren't URL clean and where NSURLRequest refuses to consult the NSURLCredentialStorage because the server isn't actually sending a HTTP 401 (for example it sends a regular page instead).

I would note mikeabdullahuk's answer is good but also if you use NSURLCredentialPersistencePermanent instead of per session it will store the credentials in the users keychain so next time you can check NSURLCredentialStorage for a non nil value for the default credentials for a protection space and if you get a non nil value you can just pass the credentials in. I am using this method right now for a delicious.com client I am writing and it works very well in my tests.

Set your credential as the default credential for the protectionspace:
// Permananent, session, whatever.
NSURLCredential *credential = [NSURLCredential credentialWithUser:username password:password persistence: NSURLCredentialPersistencePermanent];
// Make sure that if the server you are accessing presents a realm, you set it here.
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:#"blah.com" port:0 protocol:#"http" realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
// Store it
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
At this point, any subsequent NSURLConnection that is challenged using a protection space that matches what you set will use this credential

Related

Posting JSON from iOS to Rails 3.1 (Stringify_keys error)

I'm trying to create a user acount on my rails backend via json from an iPhone app. Here is what is currently being posted to the server:
Started POST "/patients" for 127.0.0.1 at 2011-11-27 20:52:29 -0800
Processing by PatientsController#create as HTML
Parameters: {"patient"=>"{\"password\":\"password\",\"password_confirmation\":\"password\",\"email\":\"testagain\"}"}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 1ms
NoMethodError (undefined method `stringify_keys' for #<String:0x00000104a354f8>):
app/controllers/patients_controller.rb:43:in `new'
app/controllers/patients_controller.rb:43:in `create'
By posting straight from the browser these are the paramaters that are submitted:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"H2iYdzdfokQs91AAozb+taMTdV2y5xLRaCni5XKQN4w=", "patient"=>{"email"=>"test", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create Patient"}
From what I have read elsewhere the stringify_keys means that the actions expects a hash (which I thought I almost reconstructed) since I am using this code to create a new user:
#patient = Patient.new(params[:patient])
I also believe that the authenticity token doesn't matter if I'm posting using JSON format... does it matter?
Over all question: Is this the right approach to be posting to a rails backend from an iphone app? Recreating the parameters hash? Would appreciate any nudges in the right direction.
For completeness sake here is the code snippet I'm using to post from my iOS app:
NSDictionary *json = [self createSignUpDictionary];
NSURL *url = [NSURL URLWithString:#"http://localhost:3000/patients"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request addRequestHeader:#"Content-Type" value:#"application/json"];
[request addRequestHeader:#"Accepts" value:#"application/json"];
[request setPostValue:[json JSONString] forKey:#"patient"];
[request startAsynchronous];
You will most likely have to disable the authentication token verification for your action.
Just put the following line in your controller and everything should work.
protect_from_forgery :except => :index
However if you do that make sure that you have some form of custom protection on your #create function. You can read this for more info: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html

http authentication in devise and rails 3

I have an application which uses devise on rails 3. I would like to enable http authentication so that I can authenticate to my web app from an iPhone app. How can I authenticate from my iPhone app to devise?
Is this secure or should I be authenticating differently?
From the design point of view you have got 3 options:
1) Use basic http authentication: your IPhone app has a secret key -which is baked in your IPhone app code - that uses to authenticate each request with the web app.
Google search: "Devise basic http authentication"
2) You can use https by having a public certificates in your IPhone app and a private certificates on your web app. This is a lot of work to configure right, it is very secure since your IPhone app and the Rails server are exchanging messages over an encrypted channel. The security is also transparent to your rails code since authentication is done at the transport level.
3) The IPhone app connects to the web app using https, get an authentication token that it then uses to make calls to the web app over regular http. More secure than 1 since the key can expire, quite a bit of work to implement and very scalable.
(http://matteomelani.wordpress.com/2011/10/17/authentication-for-mobile-devices/)
Most of apps use solution 1.
Hope this help.
EDIT: to implement http authentication (either basic or digest) I suggest you look at:
http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic.html
and
https://github.com/plataformatec/devise/wiki/How-To:-Use-HTTP-Basic-Authentication
The precise steps will depends on your Rails server stack.
EDIT 2: I do not think Devise provide a way to get the auth_token. I can see you can try several solutions:
when the user logs in the server retrieves the authentication_token and puts it in the cookie. Not very secure unless you encrypt it with a shared secret key.
you can provide a https web service that your IPhone app uses to get a user token. Your IPhone app would make the request right after receiving the user request to sign in.
Sorry I cannot be of more help with some real code.
This largely depends on how you are implementing things on the server side, but we implemented this using Matteo's 3rd option. I have a rails 3.1 implementation using devise. The route to the login is /users/login.json . First build up the JSON body for login with code like this:
NSMutableDictionary *loginDictionary = [NSMutableDictionary dictionary];
NSMutableDictionary *usernamePasswordDictionary = [NSMutableDictionary dictionary];
[usernamePasswordDictionary setObject:username forKey:#"email"];
[usernamePasswordDictionary setObject:password forKey:#"password"];
[loginDictionary setObject:usernamePasswordDictionary forKey:#"user"];
NSData *data = [NSJSONSerialization dataWithJSONObject:loginDictionary options:0 error:&error];
which yields this JSON:
{"user":{"password":"blahblahblah","email":"admin#*****.com"}}
I send a POST url request with code like this:
NSString *postUrlString = [NSString stringWithFormat:#"%#users/login.json", kServerAPIBaseURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:postUrlString] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:kTimeoutInterval];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[request setHTTPBody:data];
The response I get back contains JSON. We configured the server side to return the a session_auth_token:
{
admin = 1;
"created_at" = "2012-01-25T00:15:58Z";
"current_sign_in_at" = "2012-04-04T04:29:15Z";
"current_sign_in_ip" = "75.163.148.101";
email = "admin#******.com";
"encrypted_password" = "*****";
"failed_attempts" = 0;
id = 1;
"last_sign_in_at" = "2012-04-03T03:37:18Z";
"last_sign_in_ip" = "75.163.148.101";
"locked_at" = "<null>";
name = "Joe Smith";
"remember_created_at" = "2012-03-29T20:35:43Z";
"reset_password_sent_at" = "<null>";
"reset_password_token" = "<null>";
"session_auth_token" = "3FRgX6CYlzQJGC8tRWwqEjFaMMFKarQAYKTy3u84M0U=";
"sign_in_count" = 145;
status = 1;
"unlock_token" = "<null>";
"updated_at" = "2012-04-04T04:29:15Z";
}
We store that session_auth_token and then send it back with every request in a header, something like this:
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[self postUrlString]]...
[postRequest setHTTPMethod:#"POST"];
[postRequest setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[postRequest setValue:[self sessionAuth] forHTTPHeaderField:#"X-CSRF-Token"];
[postRequest setHTTPBody:data];
That parameter [self sessionAuth] contains the session_auth_token.
Let me know if you need clarification.

Difficulties signing in to Twitter from iPhone

I'm using MGTwitterEngine to add Twitter functionality to my app. It's very easy to simply prompt the user for a username and password and then start posting. Strangely, however, I've noticed other apps (eg Foursquare and Brightkite) require you to visit their website to associate your Twitter account with your foursquare/brightkite/whatever account.
Why do they do it this way?
Is there a reason why my app shouldn't prompt the user for a Twitter username and password, even though it would be so easy?
Thanks a bunch!
Tristan
This is because you're using Basic Auth, which is just a username/password. Most new Twitter apps use the more robust OAuth, which requires you to visit Twitter.com to allow access, but does not require a username/password. The Twitter API docs claim that support for Basic Auth will be dropped soon, so you should be using OAuth as well.
Twitter supports OAuth, which allows you to access their account without asking for their password directly.
The main concern is security. What happens if/when your database gets hacked? The attackers will have access to all of your user's twitter passwords.
On hte other hand, those sites don't store their users passwords; if they get hacked, twitter can easily disable the oauth credentials, locking out any attackers before they can do harm.
Use OAuth; it's a lot safer.
Just replace YOUR_TWITTER_USERNAME and YOUR_TWITTER_PASSWORD. The code below has to be included in your viewController.m
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL: [NSURL
URLWithString: #”http: //YOUR_TWITTER_USERNAME: YOUR_TWITTER_PASSWORD#twitter. com/
statuses/update. xml”]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval: 60. 0] ;
[theRequest setHTTPMethod: #”POST”] ;
[theRequest setHTTPBody: [[NSString stringWithFormat: #”status=%#”,
themessage] dataUsingEncoding: NSASCIIStringEncoding] ] ;
NSURLResponse* response;
NSError* error;
NSData* result = [NSURLConnection sendSynchronousRequest:theRequest
returningResponse: &response error: &error] ;
NSLog( #”%#”, [[[ NSString alloc] initWithData: result
encoding: NSASCIIStringEncoding] autorelease] ) ;

Handling redirects correctly with NSURLConnection

For the purposes of this, I'm going to pretend the original url is http://host/form and the new url is https://host/form. (Note that before I ship this, both URLs are going to be secure. However, the nonsecure-to-secure seems like a convenient redirect to test this on.)
I'm accessing a web API using NSURLConnection that redirects me. Basically, I want to take everything I just submitted to http://hostaform and re-submit it to https://host/form. I thought this would be the default behavior, but it looks like the body is being lost in the redirect.
So I think I need to handle the connection:willSendRequest:redirectResponse: event of the NSURLConnection's delegate and re-attach the body. The problem is this message seems woefully underdocumented. The only info I can find on this method is NSURLConnection Class Reference, which isn't very helpful. Among other things, it includes this:
redirectResponse: The URL response that caused the redirect. May be nil in cases where this method is not being sent as a result of involving the delegate in redirect processing.
I'm not sure what this means. Combined with an initial willSendRequest: invocation, I think this is means willSendRequest: is being sent even for my initial request, prior to the redirect response. Is that correct?
So I've added code to my delegate to retain the body an extra time, and added this willSendRequest: handler:
- (NSURLRequest *)connection: (NSURLConnection *)inConnection
willSendRequest: (NSURLRequest *)inRequest
redirectResponse: (NSURLResponse *)inRedirectResponse;
{
if (inRedirectResponse) {
NSMutableURLRequest *r = [[inRequest mutableCopy] autorelease];
[r setURL: [inRedirectResponse URL]];
[r setHTTPBody: body];
return r;
} else {
return inRequest;
}
}
It doesn't work. But I'm not even sure if this is the right approach. It seems excessively hackish to me. What should I be doing? Is this documented anywhere? I've found nothing useful in Apple's documentation or using Google so far.
(This is on the iPhone, although there doesn't seem to be much difference in these classes.)
There's a note in section 10.3.2 of RFC 2616 about this behaviour:
Note: When automatically redirecting a POST request after
receiving a 301 status code, some existing HTTP/1.0 user agents
will erroneously change it into a GET request.
So this behaviour seems to be non-standard but historical. That GET request is not a POST, and it'll be missing the payload.
Interestingly enough, this is also in the same section:
If the 301 status code is received in response to a request other
than GET or HEAD, the user agent MUST NOT automatically redirect the
request unless it can be confirmed by the user, since this might
change the conditions under which the request was issued.
That's pretty clear and seems to indicate we can't fix this, but I think ignoring this for the purpose of our own web service clients for services we pick (or control) is probably the least bad alternative.
So how do we solve this?
Instead of the willSendResponse: in the original question, I'm using this:
- (NSURLRequest *)connection: (NSURLConnection *)connection
willSendRequest: (NSURLRequest *)request
redirectResponse: (NSURLResponse *)redirectResponse;
{
if (redirectResponse) {
// we don't use the new request built for us, except for the URL
NSURL *newURL = [request URL];
// Previously, store the original request in _originalRequest.
// We rely on that here!
NSMutableURLRequest *newRequest = [_originalRequest mutableCopy];
[newRequest setURL: newURL];
return newRequest;
} else {
return request;
}
}
The idea here is that instead of cloning the new request and trying to shape it the same as the one Cocoa Touch sends me, I create a clone of the original request and change just the URL to match the request Cocoa Touch sent me. That original request is still a POST with the payload attached.
If you control the server, it's worth reading RFC 2616, section 10.3 in its entirety to see if there's a better code you can use (while checking, of course, that iOS handles the better code as it should).
You could also make a mutable copy of the redirected request and replace its HTTP method with the HTTP method of the original request. Same general principle, though that would favour keeping things from the new request rather than the old. In some circumstances that might work better, but I haven't tested this yet.
You should be checking the HTTP response status code sent by the server to determine whether to send a GET or repeat the POST. For 303 (or 302), send a GET request. For 307, repeat the POST.
i had the same problem with redirecting.
Thanks to AJSoaks!
I tried as he suggested and the problem is resolved.
So, i was trying to post the username and password through the POST method, and i saw that server redirected my request. As AJSoaks says, in case if there is 302 error you should repeat the request but this time using GET method instead of previous POST.
... at some point you have the following lines:
... it can be inside if your IBAction (button pressed) method or wherever you want...
NSMutableString *postString = [[NSMutableString alloc] init];
[postString appendString:#"username=YourUsername&password=YourPassword"];
//the original URL (https means that it supports SSL protocol)
//it doesn't change anything, don't worry about it
NSURL *URL = [NSURL URLWithString:#"https://loginWebpageURL"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", [postString length]] forHTTPHeaderField:#"Content-length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-type"];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
[NSURLConnection connectionWithRequest:request delegate:self];
[postString release];
[request release];
Than you should also implement the redirect NSURLConnection delegate method, with the following signature:
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
inside of this method, in case if you have SERVER's Error 302 or 303 you should implement something similar to the code bellow, just copy the code that you see and replace it with the new URL (redirected). The new URL you can see in the browser or if you want it is very useful, also in the future, checking it with Firebug (Firefox plugin) or Safari WEB INSPECTOR. If you use Firebug all information you can find under the "Net" option:
if (redirectResponse) {
NSLog(#"REDIRECT");
NSMutableURLRequest *requestTmp = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://areaclienti.tre.it/selfcare/areaclienti133/4552_infoCosti_ITA_HTML.xsl"]];
return [requestTmp autorelease];
}
//return original request in case thay there is no redirecting...
else return request;
NSURLConnection does not add the originalRequest headers into the redirected request in the "willSendRequest: (NSURLRequest *)inRequest".
You can workaround this problem by adding "originalRequest.headers" into the redirected request.

Unable to POST data from IPhone using google account authentication

I'm working on an IPhone application that works with a Google App Engine application. I manage to get logged by using a google account and I get the authentication token. I'm also able to GET data from the GAE service (I did it after reading another question written here) but now I need to POST data so I need to send the authentication token in the header of the POST request. I tried several options but none of them worked.
Here is the code I use to put that auth into the header:
NSString* urlStr = [NSString stringWithFormat:#"%#%#", HOST, url];
NSMutableURLRequest* urlPost = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
NSString* authStr = [NSString stringWithFormat:#"GoogleLogin auth=%#", token];
[urlPost addValue:authStr forHTTPHeaderField:#"Authorization"];
but it doesn't work.
Any help?
You need to use [request setHTTPMethod: #"POST"] and [request setHTTPBody: postdata] to properly configure the POST components. See the NSMutableURLRequest docs for more details.
Whenever I'm troubleshooting a problem related to HTTP, the first tool I'll grab is Charles HTTP Proxy. It will show you the entire request and response for closer examination.
If you're authenticating against an App Engine app, you need to obtain and send an authentication cookie, rather than using the GoogleLogin authentication. The source of appengine_rpc.py in the Python SDK demonstrates how.