{"id":894,"date":"2009-08-24T15:51:57","date_gmt":"2009-08-24T15:51:57","guid":{"rendered":"http:\/\/dalelane.co.uk\/blog\/?p=894"},"modified":"2009-08-24T15:54:46","modified_gmt":"2009-08-24T15:54:46","slug":"accessing-authenticated-google-app-engine-services-from-a-net-cf-client","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=894","title":{"rendered":"Accessing authenticated Google App Engine services from a .NET CF client"},"content":{"rendered":"<p><a href=\"http:\/\/code.google.com\/appengine\/\" target=\"_blank\">Google App Engine<\/a> (GAE) gives you an easy way to build and host web applications for free. <\/p>\n<p>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:<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">- url: \/authme\r\n  script: myAuthenticatedService.py\r\n  login: required<\/pre>\n<p>When a user goes to <a target=\"_blank\" href=\"http:\/\/yourapp.appspot.com\/authme\">http:\/\/yourapp.appspot.com\/authme<\/a> in their browser, they get taken first to a google.com logon page and promtped for their google username and password. <\/p>\n<p>Only if they authenticate correctly will Google pass them back to your page, and let them access your <code>\/authme<\/code> page. <\/p>\n<p>(<em>This is kinda nice, because as a GAE app developer, you shouldn&#8217;t need to see the user&#8217;s password. Although, I guess most users won&#8217;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.<\/em>)<\/p>\n<p>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 &#8211; this is how I&#8217;m doing it. <\/p>\n<p><!--more--><strong>Disclaimer:<\/strong> Documentation on how to do this is very lacking. I hope this means Google haven&#8217;t nailed down their authentication stuff, and aren&#8217;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.<\/p>\n<p><strong>Overview<\/strong><\/p>\n<p>With that out of the way, this is basically what you need to do to programmatically authenticate with a GAE web service.<\/p>\n<p>1.) Visit the google.com login page at <a href=\"https:\/\/www.google.com\/accounts\/ClientLogin\" target=\"_blank\">https:\/\/www.google.com\/accounts\/ClientLogin<\/a> and pass it the user&#8217;s google.com username and password. This should return you an <code>Auth<\/code> value.<\/p>\n<p>2.) Use that auth value to visit <a href=\"http:\/\/your_gae_app.appspot.com\/_ah\/login\" target=\"_blank\">http:\/\/your_gae_app.appspot.com\/_ah\/login<\/a> passing it the URL of your GAE web service as a &#8216;<code>continue<\/code>&#8216; parameter. <\/p>\n<p>3.) Accessing the login page returns you a cookie, which is used when the login page automatically redirects you to the intended destination.<\/p>\n<p>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 &#8211; which doesn&#8217;t include the classes that implement cookie management for <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.net.httpwebrequest.aspx\" target=\"_blank\">HttpWebRequest<\/a>, such as <code>CookieContainer<\/code>. <\/p>\n<p>This would&#8217;ve handled passing on the cookie returned by the redirecting page to the final target page. <\/p>\n<p>However, as cookies are basically just HTTP headers, it&#8217;s not hard to do this for yourself. Instead of letting the request auto-redirect, I do the two separate steps myself manually &#8211; accessing the login page first to retrieve the cookie, and then making a second request to the target location using that cookie.<\/p>\n<p>Here is the code to programmatically access <a href=\"http:\/\/your_gae_app_name.appspot.com\/authme\" target=\"_blank\">http:\/\/your_gae_app_name.appspot.com\/authme<\/a>. <\/p>\n<p><strong>Info you need<\/strong><\/p>\n<p>First, just definitions for the location you want to get to. <\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">String gaeAppName     = \"your_gae_app_name\";\r\nString gaeAppBaseUrl  = \"http:\/\/\" + gaeAppName + \".appspot.com\/\";\r\nString gaeAppLoginUrl = gaeAppBaseUrl + \"_ah\/login\";\r\nString yourGaeAuthUrl = gaeAppBaseUrl + \"authme\";\r\n\r\nString googleLoginUrl = \"https:\/\/www.google.com\/accounts\/ClientLogin\";<\/pre>\n<p>Next, the bits you need to collect from your user and app:<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">String googleUserName = \"user.name@gmail.com\";\r\nString googlePassword = \"canTheyTrustYou?\";\r\nString yourClientApp  = \"yourClientAppName\";<\/pre>\n<p><strong>Get the &#8216;Auth&#8217; value<\/strong><\/p>\n<p>With those bits to hand, we are ready to make the first connection &#8211; giving the username and password to the Google logon page, in return for a temporary <code>Auth<\/code> value we need in a moment.<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">\/\/ prepare the auth request\r\nHttpWebRequest authRequest = (HttpWebRequest)HttpWebRequest.Create(googleLoginUrl);\r\nauthRequest.Method = \"POST\";\r\nauthRequest.ContentType = \"application\/x-www-form-urlencoded\";\r\nauthRequest.AllowAutoRedirect = false;\r\n\r\n\/\/ prepare the data we will post to the login page\r\nString postData = \"Email=\" + UrlEncode(googleUserName) + \"&\" +\r\n                  \"Passwd=\" + UrlEncode(googlePassword) + \"&\" +\r\n                  \"service=\" + UrlEncode(\"ah\") + \"&\" +\r\n                  \"source=\" + UrlEncode(yourClientApp) + \"&\" +\r\n                  \"accountType=\" + UrlEncode(\"HOSTED_OR_GOOGLE\");\r\nbyte[] buffer = Encoding.ASCII.GetBytes(postData);\r\nauthRequest.ContentLength = buffer.Length;\r\n\r\n\/\/ submit the request\r\nStream postDataStr = authRequest.GetRequestStream();\r\npostDataStr.Write(buffer, 0, buffer.Length);\r\npostDataStr.Flush();\r\npostDataStr.Close();\r\n\r\n\/\/ get the response\r\nHttpWebResponse authResponse = (HttpWebResponse)authRequest.GetResponse();\r\nStream responseStream = authResponse.GetResponseStream();\r\nStreamReader responseReader = new StreamReader(responseStream);\r\n\r\n\/\/ look through the response for an auth line\r\nString authToken = null;\r\nString nextLine = responseReader.ReadLine();\r\nwhile (nextLine != null)\r\n{\r\n    if (nextLine.StartsWith(\"Auth=\"))\r\n    {\r\n        \/\/ remove the 'Auth=' from the start\r\n        \/\/  of the string\r\n        \/\/ because when we give it back to \r\n        \/\/  google it needs to be 'auth=' \r\n        \/\/  (lower-case 'a') and it is \r\n        \/\/  case-sensitive\r\n        authToken = nextLine.Substring(5);\r\n    }\r\n    nextLine = responseReader.ReadLine();\r\n}\r\n\r\n\/\/ cleanup\r\nresponseReader.Close();\r\nauthResponse.Close();<\/pre>\n<p>If <code>authToken<\/code> is null at this point, something went wrong &#8211; such as they entered an incorrect username and password. You can catch exceptions on reading the request, which will return &#8216;<code>Error=BadAuthentication<\/code>&#8216; in this case. <\/p>\n<p>A catch like this is a place to start:<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">catch (WebException exc)\r\n{\r\n    Stream excStream = exc.Response.GetResponseStream();\r\n    StreamReader excStreamReader = new StreamReader(excStream);\r\n    String exceptionData = excStreamReader.ReadToEnd();\r\n    excStreamReader.Close();\r\n    excStream.Close();\r\n}<\/pre>\n<p><strong>Get a cookie<\/strong><\/p>\n<p>Assuming we have an <code>authToken<\/code> value, we can move on to the next stage, which is getting the cookie we need to authenticate with our app.<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">\/\/ prepare the redirect request\r\nString cookieReqUrl = gaeAppLoginUrl + \"?\" + \r\n                      \"continue=\" + UrlEncode(yourGaeAuthUrl) + \"&\" +\r\n                      \"auth=\" + UrlEncode(authToken);\r\n\r\n\/\/ prepare our HttpWebRequest\r\nHttpWebRequest cookieRequest = (HttpWebRequest)WebRequest.Create(cookieReqUrl);\r\ncookieRequest.Method = \"GET\";\r\ncookieRequest.ContentType = \"application\/x-www-form-urlencoded\";\r\ncookieRequest.AllowAutoRedirect = false;\r\n\r\n\/\/ retrieve HttpWebResponse with the google cookie\r\nHttpWebResponse cookieResponse = (HttpWebResponse)cookieRequest.GetResponse();\r\nString googleCookie = cookieResponse.Headers[\"Set-Cookie\"];<\/pre>\n<p><strong>Use the cookie<\/strong><\/p>\n<p>If all went well, we will have an <code>ACSID<\/code> cookie in <code>googleCookie<\/code>. If it is null, then&#8230; er&#8230; you did something wrong?<\/p>\n<p>Assuming it hasn&#8217;t, we can now use this cookie to directly access the target url. <\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">\/\/ prepare our final HttpWebRequest, and set the google cookie\r\nHttpWebRequest request = (HttpWebRequest)WebRequest.Create(cookieReqUrl);\r\nrequest.Headers[\"Cookie\"] = googleCookie;\r\n\r\n\/\/ retrieve HttpWebResponse\r\nHttpWebResponse response = (HttpWebResponse)request.GetResponse();\r\n\r\n\/\/ retrieve response and read it to end into one big inefficient string :-)\r\nStream str = response.GetResponseStream();\r\nStreamReader sr = new StreamReader(str);\r\nString dataFromGAEApp = sr.ReadToEnd();\r\nsr.Close();\r\nstr.Close();<\/pre>\n<p>And there you have it &#8211; you&#8217;ve managed to access an authenticated service on a GAE app. Yay.<\/p>\n<p>There&#8217;s just one last thing to share &#8211; when creating safe URLs above, I used UrlEncode. Annoyingly, you can&#8217;t use .NET&#8217;s in-built encoding method, because it is missing in .NET Compact Framework. <\/p>\n<p>It&#8217;s pretty simple to knock up your own, though:<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">protected static string safeChars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~\";\r\n\r\nprivate static string UrlEncode(string valueToEncode)\r\n{\r\n    StringBuilder encodedValue = new StringBuilder();\r\n    foreach (char nxtChar in valueToEncode)\r\n    {\r\n        if (safeChars.IndexOf(nxtChar) != -1)\r\n        {\r\n            encodedValue.Append(nxtChar);\r\n        }\r\n        else\r\n        {\r\n            encodedValue.Append('%' + String.Format(\"{0:X2}\", (int)nxtChar));\r\n        }\r\n    }\r\n\r\n    return encodedValue.ToString();\r\n}<\/pre>\n<p>And that&#8217;s really it, minus some tidying up and error-handling.<\/p>\n<p>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&#8217;t just dump their password in cleartext in a file somewhere! <\/p>\n<p>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&#8217;d have to ask the user to re-enter their username and password again if it&#8217;s been a day since they last logged on. <\/p>\n<p><strong>Note:<\/strong> If you want to do this from a Python client, I have <a href=\"http:\/\/dalelane.co.uk\/blog\/?p=303\">written about that<\/a> before.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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: &#8211; url: \/authme script: myAuthenticatedService.py login: required When a user goes to http:\/\/yourapp.appspot.com\/authme [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[33,36,413,412,404,158,46],"class_list":["post-894","post","type-post","status-publish","format-standard","hentry","category-code","tag-netcf","tag-c","tag-clientlogin","tag-cookie","tag-gae","tag-google","tag-mobile"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/894","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=894"}],"version-history":[{"count":0,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/894\/revisions"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=894"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=894"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=894"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}