Add new backends is quite easy, usually adding just a class with a couple settings and methods overrides to retrieve user data from services API. Follow the details below.
First, lets check the common attributes for all backend types.
OAuth1 and OAuth2 provide share some common definitions based on the shared behavior during the auth process, like a successful API response from AUTHORIZATION_URL usually returns some basic user data like a user Id.
OAuth2 backends are fair simple to implement, just a few settings, a method override and it’s mostly ready to go.
The key points on this backends are:
Example code:
from social.backends.oauth import BaseOAuth2
class GithubOAuth2(BaseOAuth2):
"""Github OAuth authentication backend"""
name = 'github'
AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize'
ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token'
SCOPE_SEPARATOR = ','
EXTRA_DATA = [
('id', 'id'),
('expires', 'expires')
]
def get_user_details(self, response):
"""Return user details from Github account"""
return {'username': response.get('login'),
'email': response.get('email') or '',
'first_name': response.get('name')}
def user_data(self, access_token, *args, **kwargs):
"""Loads user data from service"""
url = 'https://api.github.com/user?' + urlencode({
'access_token': access_token
})
try:
return json.load(self.urlopen(url))
except ValueError:
return None
OAuth1 process is a bit more trickier, Twitter Docs explains it quite well. Beside the AUTHORIZATION_URL and ACCESS_TOKEN_URL attributes, a third one is needed used when starting the process.
Example code:
from xml.dom import minidom
from social.backends.oauth import ConsumerBasedOAuth
class TripItOAuth(ConsumerBasedOAuth):
"""TripIt OAuth authentication backend"""
name = 'tripit'
AUTHORIZATION_URL = 'https://www.tripit.com/oauth/authorize'
REQUEST_TOKEN_URL = 'https://api.tripit.com/oauth/request_token'
ACCESS_TOKEN_URL = 'https://api.tripit.com/oauth/access_token'
EXTRA_DATA = [('screen_name', 'screen_name')]
def get_user_details(self, response):
"""Return user details from TripIt account"""
try:
first_name, last_name = response['name'].split(' ', 1)
except ValueError:
first_name = response['name']
last_name = ''
return {'username': response['screen_name'],
'email': response['email'],
'fullname': response['name'],
'first_name': first_name,
'last_name': last_name}
def user_data(self, access_token, *args, **kwargs):
"""Return user data provided"""
url = 'https://api.tripit.com/v1/get/profile'
request = self.oauth_request(access_token, url)
content = self.fetch_response(request)
try:
dom = minidom.parseString(content)
except ValueError:
return None
return {
'id': dom.getElementsByTagName('Profile')[0].getAttribute('ref'),
'name': dom.getElementsByTagName(
'public_display_name')[0].childNodes[0].data,
'screen_name': dom.getElementsByTagName(
'screen_name')[0].childNodes[0].data,
'email': dom.getElementsByTagName(
'is_primary')[0].parentNode.getElementsByTagName(
'address')[0].childNodes[0].data,
}
OpenId is fair simpler that OAuth since it’s used for authentication rather than authorization (regardless it’s used for authorization too).
A single attribute is usually needed, the authentication URL endpoint.
Sometimes the URL is user dependant, like in myOpenId where the URL is https://<user handler>.myopenid.com. For those cases where the user must input it’s handle (or full URL). The backend must override the openid_url() method to retrieve it and return a full URL to where the user will be redirected.
Example code:
from social.backends.open_id import OpenIdAuth
from social.exceptions import AuthMissingParameter
class LiveJournalOpenId(OpenIdAuth):
"""LiveJournal OpenID authentication backend"""
name = 'livejournal'
def get_user_details(self, response):
"""Generate username from identity url"""
values = super(LiveJournalOpenId, self).get_user_details(response)
values['username'] = values.get('username') or \
urlparse.urlsplit(response.identity_url)\
.netloc.split('.', 1)[0]
return values
def openid_url(self):
"""Returns LiveJournal authentication URL"""
if not self.data.get('openid_lj_user'):
raise AuthMissingParameter(self, 'openid_lj_user')
return 'http://%s.livejournal.com' % self.data['openid_lj_user']
For others authentication types, a BaseAuth class is defined to help. Those custom auth methods must override the auth_url() and auth_complete() methods.
Example code:
from google.appengine.api import users
from social.backends.base import BaseAuth
from social.exceptions import AuthException
class GoogleAppEngineAuth(BaseAuth):
"""GoogleAppengine authentication backend"""
name = 'google-appengine'
def get_user_id(self, details, response):
"""Return current user id."""
user = users.get_current_user()
if user:
return user.user_id()
def get_user_details(self, response):
"""Return user basic information (id and email only)."""
user = users.get_current_user()
return {'username': user.user_id(),
'email': user.email(),
'fullname': '',
'first_name': '',
'last_name': ''}
def auth_url(self):
"""Build and return complete URL."""
return users.create_login_url(self.redirect_uri)
def auth_complete(self, *args, **kwargs):
"""Completes login process, must return user instance."""
if not users.get_current_user():
raise AuthException('Authentication error')
kwargs.update({'response': '', 'backend': self})
return self.strategy.authenticate(*args, **kwargs)