Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google App Engine with ClientLogin Interface for Objective-C

I'm experiencing the same problem in this previous stackoverflow.com post.

Specifically, I seem to be able to get the "Auth" token correctly, but attempts to use it in the header when I access later pages still just return me the login page's HTML.

Following links related to this post, I've determined that you need to make a subsequent call to this URL.

A call to the URL will then give you an ACSID cookie which then needs to be passed in subsequent calls in order to maintain an authenticated state.

When requesting this cookie, I've read various posts saying you need to specify your original auth token by appending it to the query string such that:

?auth=this_is_my_token

I've also read that you should set it in the http header as described in google's documentation such that a http header name/value is:

Authorization: GoogleLogin auth=yourAuthToken

I've tried both approaches and am not seeing any cookies returned. I've used Wireshark, LiveHttpHeaders for Firefox, and simple NSLog statements trying to see if anything like this is returned.

Below is the code snippet I've been using.

NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://yourapp.appspot.com/_ah/login?auth=%@", [token objectForKey:@"Auth"]]];
NSHTTPURLResponse* response;
NSError* error;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setValue:[NSString stringWithFormat:@"GoogleLogin auth=%@", [token objectForKey:@"Auth"]] forHTTPHeaderField:@"Authorization"];
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];  

//show me all header fields
NSLog([[response allHeaderFields] description]);

//show me the response
NSLog(@"%@", [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]);
NSArray * all = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:[NSURL URLWithString:@"http://yourapp.appspot.com/_ah/login"]];

//show me all cookies
for (NSHTTPCookie *cookie in all) 
{
    NSLog(@"Name: %@ : Value: %@", cookie.name, cookie.value); 
}

I hope you can use ClientLogin for Google App Engine code.

like image 382
Keith Fitzgerald Avatar asked Jan 23 '09 04:01

Keith Fitzgerald


4 Answers

Adding sample code to this question because someone contacted me directly about my solution. Note that you must set the "service" parameter equal to "ah" on the initial token request.

Initial Request of Token [done synchronously] NOTE: the "service" parameter is set to "ah" and the "source" is just set to "myapp", you should use your app name.

//create request
NSString* content = [NSString stringWithFormat:@"accountType=HOSTED_OR_GOOGLE&Email=%@&Passwd=%@&service=ah&source=myapp", [loginView username].text, [loginView password].text];
NSURL* authUrl = [NSURL URLWithString:@"https://www.google.com/accounts/ClientLogin"];
NSMutableURLRequest* authRequest = [[NSMutableURLRequest alloc] initWithURL:authUrl];
[authRequest setHTTPMethod:@"POST"];
[authRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-type"];
[authRequest setHTTPBody:[content dataUsingEncoding:NSASCIIStringEncoding]];

NSHTTPURLResponse* authResponse;
NSError* authError;
NSData * authData = [NSURLConnection sendSynchronousRequest:authRequest returningResponse:&authResponse error:&authError];  

NSString *authResponseBody = [[NSString alloc] initWithData:authData encoding:NSASCIIStringEncoding];

//loop through response body which is key=value pairs, seperated by \n. The code below is not optimal and certainly error prone. 
NSArray *lines = [authResponseBody componentsSeparatedByString:@"\n"];
NSMutableDictionary* token = [NSMutableDictionary dictionary];
for (NSString* s in lines) {
    NSArray* kvpair = [s componentsSeparatedByString:@"="];
    if ([kvpair count]>1)
        [token setObject:[kvpair objectAtIndex:1] forKey:[kvpair objectAtIndex:0]];
}

//if google returned an error in the body [google returns Error=Bad Authentication in the body. which is weird, not sure if they use status codes]
if ([token objectForKey:@"Error"]) {
    //handle error
};

The next step is to get your app running on google app engine to give you the ASCID cookie. I'm not sure why there is this extra step, it seems to be an issue on google's end and probably why GAE is not currently in their listed obj-c google data api library. My tests show I have to request the cookie in order sync with GAE. Also, notice I don't do anything with the cookie. It seems just by requesting it and getting cookied, future requests will automatically contain the cookie. I'm not sure if this is an iphone thing bc my app is an iphone app but I don't fully understand what is happening with this cookie. NOTE: the use of "myapp.appspot.com".

NSURL* cookieUrl = [NSURL URLWithString:[NSString stringWithFormat:@"http://myapp.appspot.com/_ah/login?continue=http://myapp.appspot.com/&auth=%@", [token objectForKey:@"Auth"]]];
    NSLog([cookieUrl description]);
    NSHTTPURLResponse* cookieResponse;
    NSError* cookieError;
    NSMutableURLRequest *cookieRequest = [[NSMutableURLRequest alloc] initWithURL:cookieUrl];

    [cookieRequest setHTTPMethod:@"GET"];

    NSData* cookieData = [NSURLConnection sendSynchronousRequest:cookieRequest returningResponse:&cookieResponse error:&cookieError];   

Finally, I can post json to my gae app. NOTE: the snippet below is an async request. We can handle responses by implementing didReceiveResponse, didReceiveData, didFailWIthError.

NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myapp.appspot.com/addRun?auth=%@", mytoken]];
    NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"POST"];
    [request setHTTPBody:@"my http body";

    NSURLConnection *connectionResponse = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    if (!connectionResponse) {
        NSLog(@"Failed to submit request");
    } else {
        NSLog(@"Request submitted");
    }
like image 143
Keith Fitzgerald Avatar answered Sep 17 '22 12:09

Keith Fitzgerald


Check out the code that does this in the official SDK. The latest SDK release even has it split into its own file.

like image 42
Nick Johnson Avatar answered Sep 17 '22 12:09

Nick Johnson


1st - thanks for the great post it really got me started.

2nd - I have been slugging it out with my app, trying to POST to the GAE while authenticated.

This is the request is built when POSTing, once you have acquired the authtoken:

    NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];


[request setURL:[NSURL URLWithString:url]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"image/png" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

[request setValue:authtoken forHTTPHeaderField:@"auth"];  // <-- the magic
  • mattb
like image 22
Matt B Avatar answered Sep 19 '22 12:09

Matt B


I created a few obj-c classes for implementing ClientLogin, including support for Google App Engine:

http://github.com/cameronr/GoogleAppEngineAuth

like image 37
Cam Avatar answered Sep 20 '22 12:09

Cam