Integrating registration from social sites with RPXnow

RPXnow is a service that enables users to use their OpenID, Facebook, Twitter, Google, Blogger, Myspace or other accounts to login/register on any site that use it. In the free version you can choose up to 6 providers, and in the paid one 12 (plus more features in the RPX APIs).

Setup RPXnow account

You have to register on RPXnow and create an application setting its name, and the important part - allowed domains. When you create such app you can choose providers to use

rpx1
Some providers need some setup (like Facebook), which is described on the rpxnow pages.

When you create an app it will receive an API key - you will need it to call rpxnow API methods. Put it in settings.py for example :)

How it works?

  • You place a JavaScript widget or iframe that will show the providers box.
  • Users select a provider and logs in on providers page
    rpx2
    rpx3
  • When the user logs in on the provider page it will be redirected back to your site.
  • The redirect will contain a token (POST, "token") that will allow you to get user information like mail, username, and unique identifier, based on which you can assign Django account to it.

Implementation

  • You can get the iframe and popup codes in the Quick Start tab on rpxnow site.
  • In the iframe and popup widgets you have set a full URL to a page on your site that will handle the redirect. It has to get the token and call the API for user info. It should then login or register the user. Here is a part of such view:
    def handle_rpx_post(request):
    	"""
    	RPX posting token after logging
    	"""
    	if 'token' not in request.POST:
    		return HttpResponseRedirect('/')
    	
    	# token from rpx
    	token = str(request.POST['token'])
    	# the rpxnow app key
    	apiKey = settings.RPXNOW
    	# rpxnow API url
    	url = 'https://rpxnow.com/api/v2/auth_info'
    	# we request the user data
    	req = urllib2.Request(url=url, data='token=%s&apiKey=%s' % (token, apiKey))
    	f = urllib2.urlopen(req)
    	response = f.read()
    	try:
    		jsn = json.loads(response)
    	except:
    		pass
    	else:
    		# stat == ok to poprawna autoryzacja u dostawcy
    		if jsn['stat'] == 'ok':
    			profile = jsn['profile']
    			username = profile['preferredUsername']
    			# the unique identifier - openid or link to a profile (on Facebook for example)
    			identifier = profile['identifier']
    			# not all providers provide email
    			if profile.has_key('email') and len(profile['email']) > 1:
    				email = profile['email']
    			else:
    				email = False
    
  • When we have the identifier we can check if there is a Django user account assigned to it - and if so - login user. And if there isn't any account you can create it and login new user (and connect identifier with the account).

Example code

We need a backend for loggin in user with RPX in AUTHENTICATION_BACKENDS and a model for the user-identifier relation. Below is my implementation, but you can also check django-rpx.

  • Auth backend looks like this:
    from django.contrib.auth.models import User
    from django.conf import settings
    
    from diamandas.userpanel.models import *
    
    class RPXBackend:
    	"""
    	Authenticate a user that has associated RPX to his account
    	"""
    	def authenticate(self, user_id=False, rpx=False):
    		if user_id and rpx:
    			try:
    				user = User.objects.get(id=user_id)
    			except:
    				return None
    			if rpx.user == user:
    				return user
    		return None
    
    	def get_user(self, user_id):
    		try:
    			return User.objects.get(pk=user_id)
    		except User.DoesNotExist:
    			return None
    
  • The model:
    class RPXAssociation(models.Model):
    	"""
    	Assoction of user accounts and RPX
    	"""
    	user = models.ForeignKey(User, verbose_name=_('User'), limit_choices_to={'is_staff': False})
    	identifier = models.CharField(max_length=255, verbose_name=_('Identifier'))
    	ask_for_mail = models.BooleanField(blank=True, default=False, verbose_name=_('No email specified'))
    	is_new = models.BooleanField(blank=True, default=True, verbose_name=_('Account made by social source'))
    	def __str__(self):
    		return self.identifier
    	def __unicode__(self):
    		return self.identifier
    	class Meta:
    		verbose_name = _('RPX Association')
    		verbose_name_plural = _('RPX Associations')
    
  • And the view that handles the redirect, login and registration:
    from random import choice
    import urllib2
    import json
    from django.contrib.auth import authenticate, login
    #....
    
    def handle_rpx_post(request):
    	"""
    	RPX posting token after logging
    	"""
    	if 'token' not in request.POST:
    		return HttpResponseRedirect('/')
    	
    	token = str(request.POST['token'])
    	apiKey = settings.RPXNOW
    	url = 'https://rpxnow.com/api/v2/auth_info'
    	req = urllib2.Request(url=url, data='token=%s&apiKey=%s' % (token, apiKey))
    	f = urllib2.urlopen(req)
    	response = f.read()
    	try:
    		jsn = json.loads(response)
    	except:
    		pass
    	else:
    		if jsn['stat'] == 'ok':
    			profile = jsn['profile']
    			username = profile['preferredUsername']
    			username = normalise_string(username)
    			identifier = profile['identifier']
    			if profile.has_key('email') and len(profile['email']) > 1:
    				email = profile['email']
    			else:
    				email = False
    			
    			try:
    				rpx = RPXAssociation.objects.get(identifier=identifier)
    			except:
    				# no account
    				randompass = ''.join([choice('1234567890qwertyuiopasdfghjklzxcvbnm') for i in range(7)])
    				bla = ''.join([choice('1234567890qwertyuiopasdfghjklzxcvbnm') for i in range(3)])
    				if email:
    					try:
    						user = User.objects.create_user(username, email, randompass)
    					except:
    						username = '%s_%s' % (username, bla)
    						user = User.objects.create_user(username, email, randompass)
    					r = RPXAssociation(identifier=identifier, user=user)
    				else:
    					try:
    						user = User.objects.create_user(username, randompass, randompass)
    					except:
    						username = '%s_%s' % (username, bla)
    						user = User.objects.create_user(username, randompass, randompass)
    					r = RPXAssociation(identifier=identifier, user=user, ask_for_mail=True)
    				r.save()
    				user = authenticate(user_id = str(user.id), rpx=r)
    				if user is not None:
    					login(request, user)
    					if email:
    						request.user.message_set.create(message=_('You have been logged-in successfully :)'))
    					else:
    						request.user.message_set.create(message=_('You have been logged-in successfully. You can set your email address in account preferences :) '))
    			else:
    				# we have a user for that identifier
    				user = authenticate(user_id = str(rpx.user.id), rpx=rpx)
    				if user is not None:
    					login(request, user)
    					request.user.message_set.create(message=_('You have been logged-in successfully :)'))
    			
    			return HttpResponseRedirect('/')
    	# this is error
    	return HttpResponseRedirect('/user/rpx/error/')
    
  • I've added also a view, that allows user to assign his social identifier to an existing account - user enters login and password of the existing account and if they are correct - the new account is deleted and the RPX-user relation is updated:
    class AssignRPXForm(forms.Form):
    	login = forms.CharField(label=_("Username"), max_length=30, widget=forms.TextInput())
    	password = forms.CharField(label=_("Password"), widget=forms.PasswordInput())
    
    @login_required
    def assign_rpx(request):
    	#only for autocreated accounts from RPX
    	r = RPXAssociation.objects.get(user=request.user)
    	if not r.is_new:
    		return HttpResponseRedirect('/')
    	
    	form =  AssignRPXForm()
    	if request.POST:
    		form = AssignRPXForm(request.POST)
    		if form.is_valid():
    			data = form.cleaned_data
    			# try to authenticate the user
    			user = authenticate(username=data['login'], password=data['password'])
    			if user is not None:
    				try:
    					request.user.get_profile().delete()
    				except:
    					pass
    				# delete the new user account
    				request.user.delete()
    				# login on the old account
    				login(request, user)
    				# update the rpx accociation
    				r.user = user
    				r.is_new = False
    				r.save()
    				request.user.message_set.create(message=_('You have been connected to the existing account :)'))
    				return HttpResponseRedirect('/')
    	
    	return render_to_response(
    		'userpanel/assign_rpx.html',
    		{'form': form, 'rpx': r},
    		context_instance=RequestContext(request))
    

RkBlog

Django web framework tutorials, 9 September 2009, Piotr MaliƄski

Comment article
RkBlog main page Search RSS Contact