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 authorization
API, just available as an alternative to
OAuth: Provides authorization for all Google APIs and Google also suggests to migrate application that uses to
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
- I found another one but it was not using
- I found one that uses
Djangobut 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 tokenmatching the provided settings
- If so return the original view function
- If not check if there is a matching
- If so upgrade the
request tokento an
access tokenand return the original view function
- If not get a
request token, redirect to the generated authorization url while setting the
callback urlto 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
@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.