Facebook aided registration in Django

Check out the new site at https://rkblog.dev.

Facebook introduced few days ago "Facebook registration tool" which is a registration form with some/all fields prepopulated with Facebook data. This encourages users to register:
fbreg
New widget has some useful features like custom fields to make the registration process in one stage. You can find everything in the documentation. In this article I'll show you how to setup basic implementation of this new tool in Django.

Compared to existing tools like Facebook login button or RPXnow this tool may be useful when you need more data from user than Facebook can provide with the login button or through RPX. You can display a nice one form to the user. If you don't need extra data from the user - you can stick with existing solutions.

Requirements - Facebook application

  • At start we have to create a basic Facebooku application that will be used by the registration form (if you use RPX or FB Login button you have it already). In the app settings (second tab) we have set the website domain under which we will use the form.
  • Write down the application identifier and "App Secret". You can even add that to settings.py.

Embedding the registration widget

The widget can be embedded by IFRAME or by XFBML. In case of simple iframe it will look like this:
<iframe src="http://www.facebook.com/plugins/registration.php?
             client_id=FACEBOOK_APP_ID&
             redirect_uri=http://www.youdomain.com/url/receiver/&
             fields=name,birthday,gender,location,email"
        scrolling="auto"
        frameborder="no"
        style="border:none"
        allowTransparency="true"
        width="100%"
        height="310px">
</iframe>
Where FACEBOOK_APP_ID is the identifier of your Facebook application. You have to also set the redirect_uri - URL on your website that will handle incoming registration requests from the form. When you open a page with this code you should see the registration form. If the redirect_uri isn't correct then you will get an error message.

Receiving registration data

Facebook will send the data to the specified URL via POST request. Under signed_request we will get a string containing a signature(dot)registration_data. The registration data is JSON encoded by base64. We have to split the string, decode the data and check if the signature is valid (if this is valid Facebook response and not some hacking). The signature is made from the JSON data and "App Secret" of you app.

This snippet of code (helper function + Django view) will handle checking and decoding the registration data and making a new user if he isn't registered yet:
from random import choice
import hashlib
import urllib2
import json
import base64
import hmac

def parse_signed_request(signed_request, app_secret):
    """Return dictionary with signed request data.
    Code taken from https://github.com/facebook/python-sdk"""
    try:
      l = signed_request.split('.', 2)
      encoded_sig = str(l[0])
      payload = str(l[1])
    except IndexError:
      raise ValueError("'signed_request' malformed")
    
    sig = base64.urlsafe_b64decode(encoded_sig + "=" * ((4 - len(encoded_sig) % 4) % 4))
    data = base64.urlsafe_b64decode(payload + "=" * ((4 - len(payload) % 4) % 4))

    data = json.loads(data)

    if data.get('algorithm').upper() != 'HMAC-SHA256':
      raise ValueError("'signed_request' is using an unknown algorithm")
    else:
      expected_sig = hmac.new(app_secret, msg=payload, digestmod=hashlib.sha256).digest()

    if sig != expected_sig:
      raise ValueError("'signed_request' signature mismatch")
    else:
      return data

def fb_registration(request):
	"""
	Register a user from Facebook-aided registration form
	"""
	if request.POST:
		if 'signed_request' in request.POST:
			# parse and check data
			data = parse_signed_request(request.POST['signed_request'], settings.FACEBOOK_CONNECT_SECRET)
			
			# lets try to check if user exists based on username or email
			try:
				check_user = User.objects.get(username=data['registration']['name'])
			except:
				pass
			else:
				return HttpResponseRedirect('/user/login/')
			
			try:
				check_user = User.objects.get(email=data['registration']['email'])
			except:
				pass
			else:
				return HttpResponseRedirect('/user/login/')
			
			# user does not exist. We create an account
			# in this example I assume that he will login via Facebook button or RPXnow
			# so no password is needed for him - using random password to make Django happy
			randompass = ''.join([choice('1234567890qwertyuiopasdfghjklzxcvbnm') for i in range(7)])
			user = User.objects.create_user(data['registration']['name'], data['registration']['email'], randompass)
			user.save()
			user = authenticate(username=data['registration']['name'], password=randompass)
			if user is not None:
				# save in user profile his facebook id. In this case for RPXNow login widget
				fbid = 'http://www.facebook.com/profile.php?id=%s' % data['user_id']
				r = RPXAssociation(user=user, identifier=fbid)
				r.save()
				login(request, user)
			return HttpResponseRedirect('/')
			
	return render_to_response('userpanel/fb_registration.html', {}, context_instance=RequestContext(request, userpanelContext(request)))
The view receives the data, decodes and check it then it checks if user exists (by username or email) if not it will create a user account and adds a association for RPX/FB Login button system. If the user does exist - he will be redirected to some login view.

If you want user to be able to login later on your site with login/password without the use of Facebook then you have to add a custom field for the password, or generate a random one and mail it to the user (less user friendly).

RkBlog

Django web framework tutorials, 27 December 2010


Check out the new site at https://rkblog.dev.
Comment article
Comment article RkBlog main page Search RSS Contact