Facebook Connect - Django integration on filmaster.com

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

Facebook allows developers to integrate Facebook accounts with their sites using Facebook Connect. User see a Facebook button on a site, he click/logs in on that site and the button sets Facebook cookies on it. I've made such integration for Filmaster.com -- film buffs community. In this solution users are registered/logged in automatically. There are also other solutions: fbconnect or a middleware.

The base of this integration is a middleware that checks for FB cookies (and if they are good), and then gets the user ID and try to login existing user if it has assigned Django account, or makes a new one. The FB Connect will set cookie name as you Facebook Connect API key (and few KEY_* cookies, like KEY_user with the user id).

class fbMiddleware(object):
    """
    Handle Facebook association, autologin
    """
    def process_request(self, request):
        request.facebookconn = False
        request.facebookconn_new = False
        f_name = False
        l_name = False
        # Check if we have the FBConnect cookie
        if settings.FACEBOOK_CONNECT_KEY in request.COOKIES:
            signature_hash = self.get_facebook_signature(request.COOKIES, True)
            # check if cookie is valud
            if signature_hash == request.COOKIES[settings.FACEBOOK_CONNECT_KEY]:
                # check if it didn't expired
                if datetime.fromtimestamp(float(request.COOKIES[settings.FACEBOOK_CONNECT_KEY+'_expires'])) > datetime.now():
                    # get the FB user ID from cookie
                    uid = '%s_user' % settings.FACEBOOK_CONNECT_KEY
                    cookie_uid = request.COOKIES[uid]
                    request.facebookconn = cookie_uid
                    # check if the FB user id is associated to a Django User account
                    try:
                        f = FBAssociation.objects.get(fb_uid=cookie_uid)
                    except Exception, e:
                        logging.debug("new Facebook association")
                        # no account, so we make a new one
                        password = ''.join([choice('1234567890qwertyuiopasdfghjklzxcvbnm') for i in range(10)])
                        if 'fb_name' in request.POST:
                            username = slughifi(request.POST['fb_name'])
                        else:
                            logging.error("NO Facebook username")
                            return None
                        
                        if 'fb_mail' in request.POST:
                            email = request.POST['fb_mail']
                            if len(email) >= 255:
                                # too long
                                email = str(cookie_uid)
                        else:
                            logging.error("No Facebook proxy mail")
                            return None
                        
                        username = username.replace('-', '')
                        # check if username taken
                        try:
                            u = User.objects.get(username=username)
                        except:
                            pass
                        else:
                            username = '%s%s' % (username, str(cookie_uid))
                            # this shouldn't exist
                            try:
                                u = User.objects.get(username=username)
                            except:
                                pass
                            else:
                                logging.error("Existing Facebook usernames %s" % username)
                                return None
                        
                        # check if mail is taken
                        try:
                            u = User.objects.get(email=email)
                        except:
                            pass
                        else:
                            email = str(cookie_uid)
                            try:
                                u = User.objects.get(email=email)
                            except:
                                pass
                            else:
                                logging.error("Existing proxy email %s" % email)
                                return None
                        
                        #make the user
                        user = None
                        try:
                            user = User.objects.create_user(username, email, password)
                            user.save()
                            user = authenticate(username=username, password=password)
                        except Exception, e:
                            try:
                                user.delete()
                            except:
                                pass
                            logging.error("Facebook User Creation Exception")
                            logging.error(e)
                            return None
                        else:
                            # save the Facebook association
                            o = FBAssociation(user=user, fb_uid=request.facebookconn, is_new=True, is_from_facebook=True)
                            o.save()
                        
                        if user is not None:
                            # login user and make the association
                            try:
                                login(request, user)
                            except Exception, e:
                                logging.error("Facebook User Login Exception")
                                logging.error(e)
                                return None
                            
                            if 'fb_pic' in request.POST:
                                try:
                                    path = settings.MEDIA_ROOT + date.today().strftime("avatars/%Y/%b/%d")
                                    if not os.path.isdir(path):
                                          os.makedirs(path)
                                    image = '%s/%s.jpg' %  (path, str(user.username))
                                    img = urllib2.urlopen(request.POST['fb_pic']).read()
                                    tmp = open('%s/%s.jpg' %  (path, str(user.username)), 'wb')
                                    tmp.write(img)
                                    tmp.close()
                                    i = Image.open(image)
                                    i.thumbnail((480, 480), Image.ANTIALIAS)
                                    i.convert("RGB").save(image, "JPEG")
                                    image = '%s/%s.jpg' %  (date.today().strftime("avatars/%Y/%b/%d"), str(user.username))
                                    avatar = Avatar(user=user, image=image, valid=True)
                                    avatar.save()
                                except Exception, e:
                                    logging.error("Could not save avatar from Facebook")
                                    logging.error(e)
                                    return None

                    else:
                        # FB ID is assigned to a Django User account. Login user
                        if not request.user.is_authenticated():
                            user = authenticate(user_id = f.user.id, fb_uid=cookie_uid)
                            if user is not None:
                                login(request, user)
        else:
            if request.user.is_authenticated():
                # not FB cookie but user from FB logged in? logout
                try:
                    f = FBAssociation.objects.get(user=request.user)
                except:
                    pass
                else:
                    if f.is_from_facebook:
                        logout(request)

    def get_facebook_signature(self, values_dict, is_cookie_check=False):
        """
        Generates signatures for FB requests/cookies
        """
        signature_keys = []
        for key in sorted(values_dict.keys()):
            if (is_cookie_check and key.startswith(settings.FACEBOOK_CONNECT_KEY + '_')):
                signature_keys.append(key)
            elif (is_cookie_check is False):
                signature_keys.append(key)
        
        if (is_cookie_check):
            signature_string = ''.join(['%s=%s' % (x.replace(settings.FACEBOOK_CONNECT_KEY + '_',''), values_dict[x]) for x in signature_keys])
        else:
            signature_string = ''.join(['%s=%s' % (x, values_dict[x]) for x in signature_keys])
        signature_string = signature_string + settings.FACEBOOK_CONNECT_SECRET
        
        return md5.new(signature_string).hexdigest()

If the middleware finds assigned Django user account for Facebook user id - it will login the user. If not it will create a new one, set the relation, and login new user. To create a new user it needs some data about the user. They can be obtained only with client-side JavaScript, that is called after the Facebook button login:

<fb:login-button autologoutlink="true"></fb:login-button>


<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>
	<script type="text/javascript">
	
	FB_RequireFeatures(["XFBML"], function()
		{
		FB.Facebook.init("{{ connect_key }}", "/fb/xd_receiver.htm");
		FB.Facebook.get_sessionState().waitUntilReady(function()
			{
			var uid = FB.Facebook.apiClient.get_session().uid;
			if (uid)
				{
				{% if not user.is_authenticated %}
				var viewer  = FB.Facebook.apiClient.fql_query('SELECT name, pic_small, proxied_email FROM user WHERE uid='+uid,
					function(results) {
								$.post("/", {fb_name: results[0].name, fb_pic:  results[0].pic_small , fb_mail: results[0].proxied_email} , function(data){
									{% ifequal request.path request.login_url %}
										location.assign("/dashboard/");
									{% else %}
										location.assign("http://{{ request.META.HTTP_HOST }}{{request.path }}");
									{% endifequal %}
									});
								}
					);
				{% endif %}
				}
			});
		});
	
	</script>
Data from Facebook are passed to the server with Ajax (the middleware will get them) and after that the page is refreshed - the incoming Facebook user is registered and logged in, ready to fully use the site.

Filmaster is an OpenSource Django project. You can get the code at bitbucket.org, and dev info on filmaster.org :)
RkBlog

Django web framework tutorials, 9 September 2009


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