In a project that I am currently involved, we needed to authenticate with Google Analytics in order to query statistics. As you may know currently Google supports three authentication mechanisms to allow other applications to authenticate on someone’s behalf.
ClientLogin
: You give your username, password and the application does the rest for you. I don’t think there are still people out there who can give away their passwords to applications to be used on their behalf.AuthSub
: Google’s proprietary authorizationAPI
, just available as an alternative toOAuth
.OAuth
: Provides authorization for all Google APIs and Google also suggests to migrate application that uses toOAuth
.
As can be seen from the list that there are not so many logical alternatives, the suggested way is to use OAuth
which is also an open standard. After deciding which authorization API
to use, I used some time to investigate the usage and find some practical examples. However I could not find anything that fits to my needs completely.
- I found one but that was using
gdata v1
. - I found another one but it was not using
Django
. - I found one that uses
Django
but I did not like the way it is designed.
Anyway, I decided to write my own. I spent some time thinking the best way to implement something that is pluggable and does not do stupid redirects between views and I decided to write a decorator that can be applied to Django
views. I mainly choose to do so because of the following reasons, please comment on it if you think my reasoning is not right or there is a better way to do it.
- It is declarative, means; it is pluggable
- It is clean, it does not pollutes the actual view code
- In place configuration, you can also accompany the configuration while applying it
So what I did was;
- Creating a decorator to do the authorization
- Creating a helper containing functions to load/save received tokens
- Applying the decorators wherever necessary
The following is the source of the decorator.
- It checks if there is an
access token
matching the provided settings- If so return the original view function
- If not check if there is a matching
request token
- If so upgrade the
request token
to anaccess token
and return the original view function - If not get a
request token
, redirect to the generated authorization url while setting thecallback url
to the url of the about to be executed view
- If so upgrade the
What is important is that, after a successful authorization the user will return to this view again since we are setting the callback url
accordingly before redirecting the user to Google.
import logging try: from functools import wraps except ImportError: from django.utils.functional import wraps from django.utils.decorators import available_attrs import gdata from helpers import * logger = logging.getLogger(__name__) def secure_with_gauth(view_func=None, token_prefix='default', scopes=['https://docs.google.com/feeds/'], consumer_key='anonymous', consumer_secret='anonymous', consumer_source = 'www.foo.com', error_callback=None): """ Wraps the actual view method and makes authorization for google services according to the paremters provided. """ def decorator(view_func): @wraps(view_func, assigned=available_attrs(view_func)) def _wrapped_view(request, *args, **kwargs): from django.shortcuts import redirect access_token = load_token(token_prefix, True) saved_request_token = load_token(token_prefix, False) if not isinstance(access_token, gdata.gauth.OAuthHmacToken): logger.info("Access token does not exists.") if isinstance(saved_request_token, gdata.gauth.OAuthHmacToken): client = gdata.client.GDClient(source=consumer_source) try: request_token = gdata.gauth.AuthorizeRequestToken(saved_request_token, request.build_absolute_uri()) access_token = client.GetAccessToken(request_token) save_token(access_token, token_prefix, True) except gdata.client.RequestError: if error_callback: remove_token(token_prefix, False) return error_callback(request, "Error while upgrading request token to authorization \ token, please try again later.") logger.info("Successfully retrieved access token, returning the view.") return view_func(request, *args, **kwargs) else: logger.info("Request token does not exist") client = gdata.client.GDClient(source=consumer_source) try: request_token = client.GetOAuthToken(scopes, request.build_absolute_uri(), consumer_key, consumer_secret) save_token(request_token, token_prefix, False) except gdata.client.RequestError: if error_callback: return error_callback(request, "Error while getting request token, please try again later.") logger.info("Successfully retrieved request token, redirecting to authorization url.") return redirect(request_token.generate_authorization_url().__str__()) return view_func(request, *args, **kwargs) return _wrapped_view return decorator
Applying the decorator is also very straight forward. See the following usage within views.py
.
@secure_with_gauth(token_prefix=TOKEN_PREFIX, scopes=['https://www.google.com/analytics/feeds'], consumer_source=CONSUMER_SOURCE, consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET, error_callback=generate_error) def google_analytics(request, type):
I hope it helps.