Google Data API: how to do authentication for desktop applications

4.1k views Asked by At

I wonder about the best/easiest way to authenticate the user for the Google Data API in a desktop app.

I read through the docs and it seems that my options are ClientLogin or OAuth.

For ClientLogin, it seems I have to implement the UI for login/password (and related things like saving this somewhere etc.) myself. I really wonder if there is some more support there which may pop up some default login/password screen and uses the OS keychain to store the password, etc. I wonder why such support isn't there? Wouldn't that be the standard procedure? By leaving that implementation to the dev (well, the possibility to leave that impl to the dev is good of course), I would guess that many people have come up with very ugly solutions here (when they just wanted to hack together a small script).

OAuth seems to be the better solution. However, there seems to be some code missing and/or most code I found seems only to be relevant for web applications. Esp., I followed the documentation and got here. Already in the introduction, it speaks about web application. Then later on, I need to specify a callback URL which does not make sense for a desktop application. Also I wonder what consumer key/secret I should put as that also doesn't really make sense for a desktop app (esp. not for an open-source one). I searched a bit around and it was said here (on SO) that I should use "anonymous"/"anonymous" as the consumer key/secret; but where does it say that in the Google documentation? And how do I get the token after the user has authenticated itself?

Is there some sample code? (Not with a hardcoded username/password but with a reusable full authentication method.)

Thanks, Albert


My code so far:

import gdata.gauth
import gdata.contacts.client

CONSUMER_KEY = 'anonymous'
CONSUMER_SECRET = 'anonymous'
SCOPES = [ "https://www.google.com/m8/feeds/" ] # contacts

client = gdata.contacts.client.ContactsClient(source='Test app')

import BaseHTTPServer
import SocketServer

Handler = BaseHTTPServer.BaseHTTPRequestHandler
httpd = BaseHTTPServer.HTTPServer(("", 0), Handler)
_,port = httpd.server_address

oauth_callback_url = 'http://localhost:%d/get_access_token' % port
request_token = client.GetOAuthToken(
    SCOPES, oauth_callback_url, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET)

loginurl = request_token.generate_authorization_url(google_apps_domain=None)
loginurl = str(loginurl)
import webbrowser
webbrowser.open(loginurl)

However, this does not work. I get this error:

Sorry, you've reached a login page for a domain that isn't using Google Apps. Please check the web address and try again.

I don't quite understand that. Of course I don't use Google Apps.


Ah, that error came from google_apps_domain=None in generate_authorization_url. Leave that away (i.e. just loginurl = request_token.generate_authorization_url() and it works so far.

My current code:

import gdata.gauth
import gdata.contacts.client

CONSUMER_KEY = 'anonymous'
CONSUMER_SECRET = 'anonymous'
SCOPES = [ "https://www.google.com/m8/feeds/" ] # contacts

client = gdata.contacts.client.ContactsClient(source='Test app')

import BaseHTTPServer
import SocketServer

httpd_access_token_callback = None
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith("/get_access_token?"):
            global httpd_access_token_callback
            httpd_access_token_callback = self.path
        self.send_response(200)
    def log_message(self, format, *args): pass
httpd = BaseHTTPServer.HTTPServer(("", 0), Handler)
_,port = httpd.server_address

oauth_callback_url = 'http://localhost:%d/get_access_token' % port
request_token = client.GetOAuthToken(
    SCOPES, oauth_callback_url, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET)

loginurl = request_token.generate_authorization_url()
loginurl = str(loginurl)
print "opening oauth login page ..."
import webbrowser; webbrowser.open(loginurl)

print "waiting for redirect callback ..."
while httpd_access_token_callback == None:
    httpd.handle_request()

print "done"

request_token = gdata.gauth.AuthorizeRequestToken(request_token, httpd_access_token_callback)

# Upgrade the token and save in the user's datastore
access_token = client.GetAccessToken(request_token)
client.auth_token = access_token

That will open the Google OAuth page with the hint at the bottom:

This website has not registered with Google to establish a secure connection for authorization requests. We recommend that you deny access unless you trust the website.

It still doesn't work, though. When I try to access the contacts (i.e. just a client.GetContacts()), I get this error:

gdata.client.Unauthorized: Unauthorized - Server responded with: 401, <HTML>
<HEAD>
<TITLE>Token invalid - AuthSub token has wrong scope</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<H1>Token invalid - AuthSub token has wrong scope</H1>
<H2>Error 401</H2>
</BODY>
</HTML>

Ok, it seems that I really had set the wrong scope. When I use http instead of https (i.e. SCOPES = [ "http://www.google.com/m8/feeds/" ]), it works.

But I really would like to use https. I wonder how I can do that.


Also, another problem with this solution:

In the list of Authorized Access to my Google Account, I now have a bunch of such localhost entries:

localhost:58630 — Google Contacts [ Revoke Access ]
localhost:58559 — Google Contacts [ Revoke Access ]
localhost:58815 — Google Contacts [ Revoke Access ]
localhost:59174 — Google Contacts [ Revoke Access ]
localhost:58514 — Google Contacts [ Revoke Access ]
localhost:58533 — Google Contacts [ Revoke Access ]
localhost:58790 — Google Contacts [ Revoke Access ]
localhost:59012 — Google Contacts [ Revoke Access ]
localhost:59191 — Google Contacts [ Revoke Access ]

I wonder how I can avoid that it will make such entries.

When I use xoauth_displayname, it displays that name instead but still makes multiple entries (probably because the URL is still mostly different (because of the port) each time). How can I avoid that?


My current code is now on Github.


I also wonder, where, how and for how long I should store the access token and/or the request token so that the user is not asked always again and again each time when the user starts the application.

2

There are 2 answers

10
ldx On

OAuth can be used in desktop apps as well. The consumer key and secret 'anonymous' is perfectly suitable for such an app.

The user won't authenticate themselves. You will get a token from the provider (Google) and then this will be authorized by the user as a token for a trusted consumer (your app), with which their Google service(s) can be accessed and used.

Here's a good python library for OAuth:

https://github.com/simplegeo/python-oauth2

Here's an overview on how OAuth works:

http://blog.doityourselfandroid.com/2010/11/07/google-oauth-overview/

Here's an example for Java which also explains the steps to be taken for OAuth authentication:

http://nilvec.com/implementing-client-side-oauth-on-android/

HTH.

0
hB0 On

This is how you can authenticate from Google via your desktop application. (This is how I did it) https://developers.google.com/accounts/docs/OAuth2InstalledApp https://developers.google.com/accounts/docs/OAuth2Login

Test: https://www.googleapis.com/oauth2/v1/userinfo?access_token=ACCESS_TOKEN