Features¶
- Loosely coupled.
- Tiny but powerful interface.
- The python-openid library is the only optional dependency.
- CSRF protection.
- Framework agnostic thanks to adapters. Out of the box support for Django, Flask, Pyramid and Webapp2.
- Ready to accommodate future authorization / authentication protocols.
- Makes calls to provider APIs a breeze.
- Supports asynchronous requests.
- JavaScript library as a bonus.
- Out of the box support for:
- OAuth 1.0a providers: Bitbucket, Flickr, Meetup, Plurk, Twitter, Tumblr, UbuntuOne, Vimeo, Xero, Xing and Yahoo.
- OAuth 2.0 providers: Amazon, Behance, Bitly, Cosm, DeviantART, Eventbrite, Facebook, Foursquare, GitHub, Google, LinkedIn, PayPal, Reddit, Viadeo, VK, WindowsLive, Yammer and Yandex.
- python-openid and Google App Engine based OpenID.
License¶
The package is licensed under MIT license.
Requirements¶
Requires Python 2.6 and newer. Python 3.x support added in Authomatic 0.0.11 thanks to Emmanuel Leblond.
Live Demo¶
There is a Google App Engine based live demo app running at http://authomatic-example.appspot.com which makes use of most of the features.
Contribute¶

Contributions of any kind are very welcome. If you want to contribute, please read the Development Guide
Usage¶
So you want your app to be able to log a user in with Facebook, Twitter, OpenID or whatever? First install Authomatic through PyPi,
$ pip install authomatic
or clone it from GitHub.
$ git clone git://github.com/peterhudec/authomatic.git
Note
On Google App Engine you need to include the authomatic module or a link to it inside your app’s directory.
Now it’s dead simple (hence the Deadsimpleauth). Just go through these two steps:
- Make an instance of the Authomatic class.
- Log the user in by calling the Authomatic.login() method inside a request handler.
Note
The interface of the library has recently been changed from:
import authomatic
authomatic.setup(CONFIG, 'secret')
to more flexible:
from authomatic import Authomatic
authomatic = Authomatic(CONFIG, 'secret')
The old interface will be availabe up to version 0.1.0, but you will recieve deprecation warnings in the log.
If everything goes good, you will get a User object with information like User.name, User.id or User.email. Moreover, if the user has logged in with an OAuth 2.0 or OAuth 1.0a provider, you will be able to access his/her protected resources.
Instantiate Authomatic¶
You need to pass a Config dictionary and a random secret string used for session signing and CSRF token generation to the constructor of the Authomatic class.
# main.py
import webapp2
from authomatic import Authomatic
from authomatic.adapters import Webapp2Adapter
from config import CONFIG
authomatic = Authomatic(config=CONFIG, secret='some random secret string')
The Config is a dictionary in which you configure Providers you want to use in your app. The keys are your internal provider names and values are dictionaries specifying configuration for a particular provider name.
Choose a particular provider by assigning a provider class to the "class_" key of the nested configuration dictionary. All the other keys are just keyword arguments, which will be passed to the chosen provider class constructor.
In this sample config we specify that Facebook will be available under the "fb" slug, Twitter under "tw", OpenID under "oi" and Google App Engine OpenID under "gae_oi":
# config.py
from authomatic.providers import oauth2, oauth1, openid, gaeopenid
CONFIG = {
'tw': { # Your internal provider name
# Provider class
'class_': oauth1.Twitter,
# Twitter is an AuthorizationProvider so we need to set several other properties too:
'consumer_key': '########################',
'consumer_secret': '########################',
},
'fb': {
'class_': oauth2.Facebook,
# Facebook is an AuthorizationProvider too.
'consumer_key': '########################',
'consumer_secret': '########################',
# But it is also an OAuth 2.0 provider and it needs scope.
'scope': ['user_about_me', 'email', 'publish_stream', 'read_stream'],
},
'gae_oi': {
# OpenID provider based on Google App Engine Users API.
# Works only on GAE and returns only the id and email of a user.
# Moreover, the id is not available in the development environment!
'class_': gaeopenid.GAEOpenID,
},
'oi': {
# OpenID provider based on the python-openid library.
# Works everywhere, is flexible, but requires more resources.
'class_': openid.OpenID,
}
}
Log the User In¶
Now you can log the user in by calling the authomatic.login() function inside a request handler. The request handler MUST be able to recieve both GET and POST HTTP methods. You need to pass it an adapter for your framework and one of the provider names which you specified in the keys of your Config. We will get the provider name from the URL slug.
class Login(webapp2.RequestHandler):
def any(self, provider_name):
result = authomatic.login(Webapp2Adapter(self), provider_name)
The authomatic.login() function will redirect the user to the provider, which will prompt him/her to authorize your app (the consumer) to access his/her protected resources (OAuth 1.0a and OAuth 2.0), or to verify his/her claimed ID (OpenID). The provider then redirects the user back to this request handler.
If the login procedure is over, authomatic.login() returns a LoginResult. You can check for errors in LoginResult.error or in better case for a User in LoginResult.user. The User object has plenty of useful properties.
Warning
Do not write anything to the response unless the login procedure is over! The authomatic.login() either returns None, which means that the login procedure si still pending, or a LoginResult which means that the login procedure is over.
Check whether login procedure is over.
if result:
self.response.write('<a href="..">Home</a>')
Check for errors, but hope that there is a LoginResult.user. If so, we have an authenticated user logged in. Before we print a welcoming message we need to update the User to get more info about him/her.
if result.error:
self.response.write(u'<h2>Damn that error: {}</h2>'.format(result.error.message))
elif result.user:
if not (result.user.name and result.user.id):
result.user.update()
self.response.write(u'<h1>Hi {}</h1>'.format(result.user.name))
self.response.write(u'<h2>Your id is: {}</h2>'.format(result.user.id))
self.response.write(u'<h2>Your email is: {}</h2>'.format(result.user.email))
Advanced¶
Logging a user in is nice, but you can do more.
You can use the user’s credentials to access his/her protected resources, make asynchronous requests, use your own session implementation and Save your backend’s resources by utilizing the authomatic.js javascript library.
Credentials¶
If the User has User.credentials, he/she is logged in either with OAuth 1.0a or OAuth 2.0, both of which are subclasses of AuthorizationProvider. That means, that we can access the user’s protected resources. Lets get the user’s five most recent facebook statuses.
if result.user.credentials:
if result.provider.name == 'fb':
self.response.write('Your are logged in with Facebook.<br />')
url = 'https://graph.facebook.com/{}?fields=feed.limit(5)'
url = url.format(result.user.id)
response = result.provider.access(url)
The call returns a Response object. The Response.data contains the parsed response content.
if response.status == 200:
statuses = response.data.get('feed').get('data')
error = response.data.get('error')
if error:
self.response.write(u'Damn that error: {}!'.format(error))
elif statuses:
self.response.write('Your 5 most recent statuses:<br />')
for message in statuses:
text = message.get('message')
date = message.get('created_time')
self.response.write(u'<h3>{}</h3>'.format(text))
self.response.write(u'Posted on: {}'.format(date))
else:
self.response.write('Damn that unknown error!<br />')
self.response.write(u'Status: {}'.format(response.status))
Credentials can be serialized to a lightweight url-safe string.
serialized_credentials = credentials.serialize()
It would be useless if they could not be deserialized back to original.
Note
The deserialization of the credentials is dependent on the Config used when the credentials have been serialized. You can deserialize them in a different application as long as you use the same Config.
credentials = authomatic.credentials(serialized_credentials)
They know the provider name which you specified in the Config.
provider_name = credentials.provider_name
OAuth 2.0 credentials have limited lifetime. You can check whether they are still valid, in how many seconds they expire, get the date and time or UNIX timestamp of their expiration and find out whether they expire soon.
valid = credentials.valid # True / False
seconds_remaining = credentials.expire_in
expire_on = credentials.expiration_date # datetime.datetime()
expire_on = credentials.expiration_time # 1362080855
should_refresh = credentials.expire_soon(60 * 60 * 24) # True if expire in less than one day
You can refresh the credentials while they are still valid. Otherwise you must repeat the authomatic.login() procedure to get new credentials.
if credentials.expire_soon():
response = credentials.refresh()
if response and response.status == 200:
print 'Credentials have been refreshed successfully.'
Finally use the credentials (serialized or deserialized) to access protected resources of the user to whom they belong by passing them to the authomatic.access() function along with the resource URL.
response = authomatic.access(credentials, 'https://graph.facebook.com/{id}?fields=birthday')
You can find out more about Credentials in the Reference. There is also a short tutorial about credentials in the Tutorials / Examples section.
Asynchronous Requests¶
Following functions fetch remote URLs and block the current thread till they return a Response.
If you need to call more than one of them in a single request handler, or if there is another time consuming task you need to do, there is an asynchronous alternative to each of these functions.
- authomatic.async_access()
- AuthorizationProvider.async_access()
- User.async_update()
- Credentials.async_refresh()
Warning
The internal implementation of the future pattern is quite naive. Use with caution!
These asynchronous alternatives all return a Future instance which represents the separate thread in which their synchronous brethren are running. You should call all the asynchronous functions you want to use at once, then do your time consuming tasks and finally collect the results of the functions by calling the get_result() method of each of the Future instances.
# These guys will run in parallel and each returns immediately.
user_future = user.async_update()
credentials_future = user.credentials.async_refresh()
foo_future = authomatic.access(user.credentials, 'http://api.example.com/foo')
bar_future = authomatic.access(user.credentials, 'http://api.example.com/bar')
# Do your time consuming task.
time.sleep(5)
# Collect results:
# Updates the User instance in place and returns response.
user_response = user_future.get_result()
if user_response.status == 200:
print 'User was updated successfully.'
# Refreshes the Credentials instance in place and returns response.
credentials_response = credentials_future.get_result()
if credentials_response.status == 200:
print 'Credentials were refreshed successfully.'
foo_response = foo_future.get_result()
bar_response = bar_future.get_result()
Session¶
The authomatic.login() function uses a default secure cookie based session to store state during the login procedure. If you want to use different session implementation you can pass it together with its save method to the authomatic.login() function. The only requirement is that the session implementation must have a dictionary-like interface.
Note
The default secure cookie based session will be deleted immediately after the login procedure is over. Custom sessions however, will be preserved.
import webapp2
from webapp2_extras import sessions
import authomatic
from authomatic.adapters import Webapp2Adapter
class Login(webapp2.RequestHandler):
def any(self, provider_name):
# Webapp2 session
session_store = sessions.get_store(request=self.request)
session = session_store.get_session()
session_saver = lambda: session_store.save_sessions(self.response)
result = authomatic.login(Webapp2Adapter(self),
provider_name,
session=session,
session_saver=session_saver)
Man, isn’t there a simpler way to make a Webapp2 session? You guessed it didn’t you? There is one in the authomatic.extras.gae module:
import webapp2
import authomatic
from authomatic.adapters import Webapp2Adapter
from authomatic.extras import gae
class Login(webapp2.RequestHandler):
def any(self, provider_name):
# Creates a new Webapp2 session.
session = gae.Webapp2Session(self, secret='your-super-confidential-secret')
result = authomatic.login(Webapp2Adapter(self),
provider_name,
session=session,
session_saver=session.save)
If you are already using a Webapp2 session you can do it like this:
import webapp2
import authomatic
from authomatic.adapters import Webapp2Adapter
from authomatic.extras import gae
class Login(webapp2.RequestHandler):
def any(self, provider_name):
# Wraps an existing Webapp2 session.
session = gae.Webapp2Session(self, session=self.session)
result = authomatic.login(Webapp2Adapter(self),
provider_name,
session=session,
session_saver=session.save)
JavaScript¶
Popup¶
The authomatic.login() function redirects the user to the provider to ask him for his/her consent. If you rather want to make the redirect in a popup, the authomatic.popupInit() function of the javascript.js library with conjunction with LoginResult.js_callback() make it a breeze.
Just add the authomatic class to your login handler links and forms and change their default behavior by calling the authomatic.popupInit() function. The elements will now open a 600 x 800 centered popup on click or submit respectively. you can change the popup dimensions with the authomatic.setup().
Set the onLoginComplete event handler to the authomatic.setup() function, which should accept a result argument.
<!DOCTYPE html>
<html>
<head>
<title>Login Popup Example<title>
<!-- authomatic.js is dependent on jQuery -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="authomatic.js"></script>
</head>
<body>
<!-- Opens a popup with location = "login/facebook" -->
<a class="authomatic" href="login/facebook">Login with Facebook</a>
<!-- Opens a popup with location = "login/openid?id=me.yahoo.com" -->
<form class="authomatic" action="login/openid" method="GET">
<input type="text" name="id" value="me.yahoo.com" />
<input type="submit" value="Login with OpenID" />
</form>
<script type="text/javascript">
// Set up the library
authomatic.setup({
popupWidth: 600,
popupHeight: 400,
onLoginComplete: function(result) {
// Handle the login result when the popup closes.
if (result.user) {
alert('Hi ' + result.user.name);
} else if (result.error) {
alert('Error occurred: ' + result.error.message);
}
}
});
// Change behavior of links and form of class="authomatic"
authomatic.popupInit();
</script>
</body>
</html>
In your login handler just write the return value of the LoginResult.js_callback() method to the response.
import webapp2
import authomatic
from authomatic.adapters import Webapp2Adapter
class Login(webapp2.RequestHandler):
def any(self, provider_name):
result = authomatic.login(Webapp2Adapter(self), provider_name)
if result:
if result.user:
result.user.update()
self.response.write(result.js_callback())
The LoginResult.js_callback() generates a HTML that closes the popup when the login procedure is over and triggers the onLoginComplete event handler with a JSON login result object passed as argument. The login result object has similar structure as the LoginResult.
Access¶
Accessing the user’s protected resources and provider APIs is very easy thanks to the authomatic.access() function, but you could save your backend’s resources by delegating it to the user’s browser.
This however is easier said then done because some providers do not support cross-domain and JSONP requests and all OAuth 1.0a request need to be signed with the consumer secret by the backend. Leave alone special request requirements invented by some zealous providers on top of the OAuth 1.0a and OAuth 2.0 standards.
The authomatic.access() function of the javascript.js library solves this for you. It encapsulates solutions of all the aforementioned issues and always makes the request in the most efficient way.
authomatic.access(loginResult.user.credentials, 'https://graph.facebook.com/{id}/feed',{
substitute: loginResult.user, // replaces the {id} in the URL with loginResult.user.id.
onAccessSuccess: function(data) {
alert('Your most recent status is: ' + data.data[0].story);
},
onAccessComplete: function(textStatus) {
if (textStatus == 'error') {
alert('We were unable to get your Facebook feed!');
}
}
});