This one has been bugging me for a few days, and with help from a very helpful Google engineer I’ve finally got this working, so I thought I’d share my code where the next poor soul to try and do it might find it!
The problem:
I’ve written a small web service which I am hosting on Google App Engine.
By adding “login: required
” to specific services in the app’s app.yaml file, I can make sure that you need to login to access a service.
If you’re accessing the service from a web browser, this is fine – you get redirected to a Google login page, and after entering your username and password and hitting ‘submit’, you get sent on to the web service you wanted.
But how do you do that programmatically? I wanted to do it from a Python application on my desktop, without being able to navigate the HTML login form.
I tried asking for help on Stack Overflow, and within a couple of hours, someone pointed me in the right direction. (Which is very cool, by the way)
A simplified version of my current code is below.
In short, you use the Google ClientLogin API to get an AuthToken.
You use this AuthToken to get a cookie from http://mylovelyapp.appspot.com/_ah/login/
.
Easy when you know how!
import os import urllib import urllib2 import cookielib users_email_address = "billy.bob@gmail.com" users_password = "billybobspassword" target_authenticated_google_app_engine_uri = 'http://mylovelyapp.appspot.com/mylovelypage' my_app_name = "yay-1.0" # we use a cookie to authenticate with Google App Engine # by registering a cookie handler here, this will automatically store the # cookie returned when we use urllib2 to open http://mylovelyapp.appspot.com/_ah/login cookiejar = cookielib.LWPCookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar)) urllib2.install_opener(opener) # # get an AuthToken from Google accounts # auth_uri = 'https://www.google.com/accounts/ClientLogin' authreq_data = urllib.urlencode({ "Email": users_email_address, "Passwd": users_password, "service": "ah", "source": my_app_name, "accountType": "HOSTED_OR_GOOGLE" }) auth_req = urllib2.Request(auth_uri, data=authreq_data) auth_resp = urllib2.urlopen(auth_req) auth_resp_body = auth_resp.read() # auth response includes several fields - we're interested in # the bit after Auth= auth_resp_dict = dict(x.split("=") for x in auth_resp_body.split("\n") if x) authtoken = auth_resp_dict["Auth"] # # get a cookie # # the call to request a cookie will also automatically redirect us to the page # that we want to go to # the cookie jar will automatically provide the cookie when we reach the # redirected location # this is where I actually want to go to serv_uri = target_authenticated_google_app_engine_uri serv_args = {} serv_args['continue'] = serv_uri serv_args['auth'] = authtoken full_serv_uri = "http://mylovelyapp.appspot.com/_ah/login?%s" % (urllib.urlencode(serv_args)) serv_req = urllib2.Request(full_serv_uri) serv_resp = urllib2.urlopen(serv_req) serv_resp_body = serv_resp.read() # serv_resp_body should contain the contents of the # target_authenticated_google_app_engine_uri page - as we will have been # redirected to that page automatically # # to prove this, I'm just gonna print it out print serv_resp_body
Note: If you’re going to copy and paste this code, be aware that WordPress sometimes screws with single and double-quotes in my posts. So you might need to go through and check they’re all okay.
Update: If you want to do this from a C# client, I have written a post about how to do it.
Tags: google, google app engine, python, stackoverflow
Hi: I am trying to do something similar. I followed this. When I use it locally (http://localhost), I still get the login url as the response. Any clues?
I’ve not tried this with the development server so I don’t know the answer for sure, sorry.
But in principle, I’m not surprised to hear that it didn’t work. As you probably know, the development server uses a different approach to logins and authentication (e.g. you know the simple blue login box with no password, compared with the normal Google login page?) so I wouldn’t expect a cookie you get from the ClientLogin API to be compatible with it.
If you only want this to work on the development server then it might be easier to spoof filling in the fake login form?
I implemented this is c#, and I get the AuthToken just fine. However, the continue URL does not see the user as logged in (users.get_current_user()) is not returning a user.
I’m new to this, and have written an API using google app engine, and now I want to access the API via a c# WPF application. The problem is in authenticating and having the API know the user that was authenticated.
You mentioned a cookie. I’m not sure how I can use this or access this in c#, and the ClientLogin documentation says to use the token in all subsequent service calls.
Any ideas?
I got it! I did miss the cookie part, and had to set a CookieContainer on my HttpWebRequest. Works great now. Thanks.
Thanks for sharing this, it’s been a great help. Using your code I now can access my google app engine services from the command line. But (new to python) I can only figure out out to make GET and POST requests. I also need to make PUT requests to fully use my service, but I can’t figure out how to make urllib2 do a put request.
Is that a problem that you’ve run into? Any solution? Thanks again for posting this and for any ideas that you have on PUT requests.
@Jesse – This post is worth a look: http://benjamin.smedbergs.us/blog/2008-10-21/putting-and-deleteing-in-python-urllib2/
Hi, Your codes works very well. Thanks! I’ve been looking for it for a while.
I put your code into a class and it’s easy for me to use.
import os
import urllib
import urllib2
import cookielib
class xAppAuth:
def __init__(self,user,password,appName):
self.user = user
self.password = password
self.appName = appName
self.authtoken = None
def getAuthtoken(self,Refresh = False):
if self.authtoken is None or Refresh:
cookiejar = cookielib.LWPCookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
urllib2.install_opener(opener)
auth_uri = ‘https://www.google.com/accounts/ClientLogin’
authreq_data = urllib.urlencode({ “Email”: self.user,
“Passwd”: self.password,
“service”: “ah”,
“source”: self.appName,
“accountType”: “HOSTED_OR_GOOGLE” })
auth_req = urllib2.Request(auth_uri, data=authreq_data)
auth_resp = urllib2.urlopen(auth_req)
auth_resp_body = auth_resp.read()
# auth response includes several fields – we’re interested in
# the bit after Auth=
auth_resp_dict = dict(x.split(“=”)
for x in auth_resp_body.split(“\n”) if x)
self.authtoken = auth_resp_dict[“Auth”]
return self.authtoken
def getAuthUrl(self,Uri,AppName):
serv_uri = Uri
serv_args = {}
serv_args[‘continue’] = serv_uri
serv_args[‘auth’] = self.getAuthtoken()
return “http://”+AppName+”.appspot.com/_ah/login?%s” % (urllib.urlencode(serv_args))
def getAuthRequest(self,Uri,AppName):
return urllib2.Request(self.getAuthUrl(Uri,AppName))
def getAuthResponse(self,Uri,AppName):
return urllib2.urlopen(self.getAuthRequest(Uri,AppName))
def getAuthRead(self,Uri,AppName):
return self.getAuthResponse(Uri,AppName).read()
hi –
i’m experiencing the same problem but have not been able to find a solution. in fact, i’ve opened a new thread on stackoverflow.com. Drop me a line if you can help out at all!
Thank you for share!!. Now I can post data in my application!
[…] I’ve figured out how to authenticate with Google App Engine from C#, I’ll do this properly from the app rather than embedding a […]
[…] If you want to do this from a Python client, I have written about that […]
I have problems posting data. I modified this line:
serv_req = urllib2.Request(full_serv_uri)
with:
serv_req = urllib2.Request(full_serv_uri, data)
data is a form encoded dictionary, but the server doesn’t receive any POST or GET data.
If I configure the application without login: required
– url: /gestion/ventas/.*
script: main.py
secure: always
It works well.
Has anyone worked out POSTing data?
@Pere
I’m afraid that the short answer is “I don’t know” because I haven’t needed to try doing a POST.
At a guess (and I’m just offering this as something that I would try investigating if I were you – I don’t know if this is the answer), it could be this.
(I’ve written a better explanation about how the Google auth stuff works at https://dalelane.co.uk/blog/?p=894 which may be useful in understanding my idea here.)
Let’s say that your Google App is at uri http://yourapp.appspot.com/gestion/ventas/something – for now, let’s call this “urlY”
You need a cookie to get to urlY
So you go to the login uri http://yourapp.appspot.com/_ah/login – for now, let’s call this “urlX”
Visiting urlX returns a cookie, which you can use to access urlY
In my example here, I used http://urlX?continue=urlY
This means you visit urlX, get the cookie, and ask that page to redirect you on to urlY
As it is written, you don’t submit the request directly to urlY
The point I’m trying to highlight is that you appear to be modifying the request to “full_serv_uri”. This is the request to urlX – the login page. Maybe this wants a GET? Maybe this only supports a GET? Maybe when you try submitting a POST to urlX, the reason you don’t see the request reach your server is because urlX doesn’t like a POST with a “data” variable in the body and a “continue” variable in the URL.
If I was you, I would try breaking these two requests apart.
1) Visit urlX and get the cookie – but don’t allow the auto-redirect. Use GET. Just visit that login page and just get the cookie
2) Visit urlY using the cookie. Use your POST directly to the url you want.
You can see the sort of thing I mean at https://dalelane.co.uk/blog/?p=894 – albeit in C#, but hopefully you get the idea.
Does this help?
Please do let me know whether this is the answer or not – I’d be curious to hear.
Kind regards
Dale
Hello dale,
You are right!
To do an auth POST we only need to get the cookie and then use it in the next POSTs.
Using the youngfe Object-oriented-version of your code:
# 1st.- get the ASCID cookie
peticio = xAppAuth(USER, PASSW, APPLICATION)
cookie =peticio.getAuthtoken()
# 2nd – POST data
try:
r=”
req=urllib2.Request(self.url, datos)
res=urllib2.urlopen(req)
if res.code==200:
r=res.read()
return r
except urllib2.HTTPError, e:
return “Error Http “+str(e.code)
except urllib2.URLError, e:
return “Error url “+str(e.reason)
except Exception, e:
return “Error servidor “+str(e)
Thank you very much for your aid.
Kind regards,
Pere
Thanks for this Dale, it’s a been a big help to have this sort of script available online 🙂
Note that Google has recently changed the way authorization failure is indicated. They used to place an Error token in the response. Now they just return a 403 (Forbidden) status. This broke my code!
I’m also trying to get POST working. I understand why the redirect causes the original post to not work, but I still don’t understand how to fix it. That is, how to get the cookie with one get request, store it, and then send it with the follow up post request.
…
after some more work and some googling, found this solution, which seems to be based on what you provided.
http://code.activestate.com/recipes/577217-routines-for-programmatically-authenticating-with-/
The final piece was to take the cookie returned by that code, put it in a dictionary
cookieDict = {“Cookie”: get_gae_cookie(authToken)}
and pass that in as a header when creating the request
request =urllib2.Request(url, encodedPostParams, headers = cookieDict)