Google App Engine (GAE) gives you an easy way to build and host web applications for free.
For any address you specify in your GAE app, you can require users to be authenticated. For example, if you have this in your app.yaml:
- url: /authme script: myAuthenticatedService.py login: required
When a user goes to http://yourapp.appspot.com/authme in their browser, they get taken first to a google.com logon page and promtped for their google username and password.
Only if they authenticate correctly will Google pass them back to your page, and let them access your /authme
page.
(This is kinda nice, because as a GAE app developer, you shouldn’t need to see the user’s password. Although, I guess most users won’t make a distinction between typing in their username and password into the google.com login page and into a login form on an appspot.com page.)
If you are writing browser-delivered apps, this is all fine and works as described. This is slightly trickier if you are writing a web service that you want to be accessed by a client app. I wanted to access a GAE web service from a mobile client – this is how I’m doing it.
Disclaimer: Documentation on how to do this is very lacking. I hope this means Google haven’t nailed down their authentication stuff, and aren’t looking to support or encourage the current implementation until they have a proper polished API. At any rate, for my hobbyist-hacking purposes, this is fine. If you have a more important purpose in mind, you might want to think twice.
Overview
With that out of the way, this is basically what you need to do to programmatically authenticate with a GAE web service.
1.) Visit the google.com login page at https://www.google.com/accounts/ClientLogin and pass it the user’s google.com username and password. This should return you an Auth
value.
2.) Use that auth value to visit http://your_gae_app.appspot.com/_ah/login passing it the URL of your GAE web service as a ‘continue
‘ parameter.
3.) Accessing the login page returns you a cookie, which is used when the login page automatically redirects you to the intended destination.
I wanted to write a client for Windows Mobile that could access an authenticated GAE location. This is slightly more complex as the flavour of .NET that you get for Windows Mobile is the .NET Compact Framework – which doesn’t include the classes that implement cookie management for HttpWebRequest, such as CookieContainer
.
This would’ve handled passing on the cookie returned by the redirecting page to the final target page.
However, as cookies are basically just HTTP headers, it’s not hard to do this for yourself. Instead of letting the request auto-redirect, I do the two separate steps myself manually – accessing the login page first to retrieve the cookie, and then making a second request to the target location using that cookie.
Here is the code to programmatically access http://your_gae_app_name.appspot.com/authme.
Info you need
First, just definitions for the location you want to get to.
String gaeAppName = "your_gae_app_name"; String gaeAppBaseUrl = "http://" + gaeAppName + ".appspot.com/"; String gaeAppLoginUrl = gaeAppBaseUrl + "_ah/login"; String yourGaeAuthUrl = gaeAppBaseUrl + "authme"; String googleLoginUrl = "https://www.google.com/accounts/ClientLogin";
Next, the bits you need to collect from your user and app:
String googleUserName = "user.name@gmail.com"; String googlePassword = "canTheyTrustYou?"; String yourClientApp = "yourClientAppName";
Get the ‘Auth’ value
With those bits to hand, we are ready to make the first connection – giving the username and password to the Google logon page, in return for a temporary Auth
value we need in a moment.
// prepare the auth request HttpWebRequest authRequest = (HttpWebRequest)HttpWebRequest.Create(googleLoginUrl); authRequest.Method = "POST"; authRequest.ContentType = "application/x-www-form-urlencoded"; authRequest.AllowAutoRedirect = false; // prepare the data we will post to the login page String postData = "Email=" + UrlEncode(googleUserName) + "&" + "Passwd=" + UrlEncode(googlePassword) + "&" + "service=" + UrlEncode("ah") + "&" + "source=" + UrlEncode(yourClientApp) + "&" + "accountType=" + UrlEncode("HOSTED_OR_GOOGLE"); byte[] buffer = Encoding.ASCII.GetBytes(postData); authRequest.ContentLength = buffer.Length; // submit the request Stream postDataStr = authRequest.GetRequestStream(); postDataStr.Write(buffer, 0, buffer.Length); postDataStr.Flush(); postDataStr.Close(); // get the response HttpWebResponse authResponse = (HttpWebResponse)authRequest.GetResponse(); Stream responseStream = authResponse.GetResponseStream(); StreamReader responseReader = new StreamReader(responseStream); // look through the response for an auth line String authToken = null; String nextLine = responseReader.ReadLine(); while (nextLine != null) { if (nextLine.StartsWith("Auth=")) { // remove the 'Auth=' from the start // of the string // because when we give it back to // google it needs to be 'auth=' // (lower-case 'a') and it is // case-sensitive authToken = nextLine.Substring(5); } nextLine = responseReader.ReadLine(); } // cleanup responseReader.Close(); authResponse.Close();
If authToken
is null at this point, something went wrong – such as they entered an incorrect username and password. You can catch exceptions on reading the request, which will return ‘Error=BadAuthentication
‘ in this case.
A catch like this is a place to start:
catch (WebException exc) { Stream excStream = exc.Response.GetResponseStream(); StreamReader excStreamReader = new StreamReader(excStream); String exceptionData = excStreamReader.ReadToEnd(); excStreamReader.Close(); excStream.Close(); }
Get a cookie
Assuming we have an authToken
value, we can move on to the next stage, which is getting the cookie we need to authenticate with our app.
// prepare the redirect request String cookieReqUrl = gaeAppLoginUrl + "?" + "continue=" + UrlEncode(yourGaeAuthUrl) + "&" + "auth=" + UrlEncode(authToken); // prepare our HttpWebRequest HttpWebRequest cookieRequest = (HttpWebRequest)WebRequest.Create(cookieReqUrl); cookieRequest.Method = "GET"; cookieRequest.ContentType = "application/x-www-form-urlencoded"; cookieRequest.AllowAutoRedirect = false; // retrieve HttpWebResponse with the google cookie HttpWebResponse cookieResponse = (HttpWebResponse)cookieRequest.GetResponse(); String googleCookie = cookieResponse.Headers["Set-Cookie"];
Use the cookie
If all went well, we will have an ACSID
cookie in googleCookie
. If it is null, then… er… you did something wrong?
Assuming it hasn’t, we can now use this cookie to directly access the target url.
// prepare our final HttpWebRequest, and set the google cookie HttpWebRequest request = (HttpWebRequest)WebRequest.Create(cookieReqUrl); request.Headers["Cookie"] = googleCookie; // retrieve HttpWebResponse HttpWebResponse response = (HttpWebResponse)request.GetResponse(); // retrieve response and read it to end into one big inefficient string :-) Stream str = response.GetResponseStream(); StreamReader sr = new StreamReader(str); String dataFromGAEApp = sr.ReadToEnd(); sr.Close(); str.Close();
And there you have it – you’ve managed to access an authenticated service on a GAE app. Yay.
There’s just one last thing to share – when creating safe URLs above, I used UrlEncode. Annoyingly, you can’t use .NET’s in-built encoding method, because it is missing in .NET Compact Framework.
It’s pretty simple to knock up your own, though:
protected static string safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; private static string UrlEncode(string valueToEncode) { StringBuilder encodedValue = new StringBuilder(); foreach (char nxtChar in valueToEncode) { if (safeChars.IndexOf(nxtChar) != -1) { encodedValue.Append(nxtChar); } else { encodedValue.Append('%' + String.Format("{0:X2}", (int)nxtChar)); } } return encodedValue.ToString(); }
And that’s really it, minus some tidying up and error-handling.
It does leave you with the question of what to do the next time your user wants to logon. All I would say is please don’t just dump their password in cleartext in a file somewhere!
You could store the cookie that you get back to re-use it, but the Cookie that gets returned has an expiry time of 24 hours, so you’d have to ask the user to re-enter their username and password again if it’s been a day since they last logged on.
Note: If you want to do this from a Python client, I have written about that before.
[…] Update: If you want to do this from a C# client, I have written a post about how to do it. […]
Nice post. I didn’t use the Google User login feature of App Engine because of its limited user base (yes, not everyone has an Google account … yet) and provided a simple account (completed by Twitter and/or Facebook info through their delegated authentication APIs). That way, you “just have to” generate a token and return it in your auth handler, and verify it for each subsequent request. Of course, this way you don’t benefit from the global Google Account , but that’s a trade-off I’m willing to make in order to gain more flexibility ( since you can define expiration time, throttling and all) and broaden the user base. Haven’t had the time to put up a C# client yet, but that’s in the todo list. Thanks for sharing your code 🙂
Thanks a lot! Works like a charm!
I am becoming a regular reader of your blog, please keep posting your great stuff!
Thanks for the post, Nice to-the-point example. C# documentation is pretty rare in the 🙂 Google App Engine world.
I’ve submitted it on GAE Cupboard.