On my site, I have some resources that are private, such as certain blog entries and photos. I wanted to make it so that my Facebook friends could log into my site without me having to create Django-based accounts for them. It turned out to be fairly trivial to get things set up, but I thought I'd share what I did in case it can be of use to anyone else.


Set up Facebook Connect

Follow the Basic guide from the Facebook Developers WIki. For the cross-domain communication channel file step, you could use a flat page or set up a custom URL and template. I opted to set up a custom URL and template. Create a new template in your templates directory named xd_receiver.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
    <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js" type="text/javascript"></script>
</body>
</html>
Set up a view for this page by adding the following to views.py:
def xd_receiver(request):
    return render_to_response('xd_receiver.html')
Configure a URL to serve this page by adding the appropriate values to urls.py:
from myproject.views import xd_receiver
...
(r'^xd_receiver\.html$', xd_receiver),
After completing the rest of the Facebook guide, you should be able to log in using the Facebook Connect button on the page where you added the
<fb:login-button>
code.


Adding FacebookConnectMiddleware

I chose to use custom middleware to look for the presence of the Facebook Connect cookies that are set when a user has logged in to Facebook Connect on my site. This middleware does the following: Create a new middleware file namedFacebookConnectMiddleware.py and place the following code in it.
# FacebookConnectMiddleware.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.conf import settings

import md5
import urllib
import time
import simplejson
from datetime import datetime

# These values could be placed in Django's project settings
API_KEY = 'YOUR KEY FROM FACEBOOK'
API_SECRET = 'YOUR SECRET FROM FACEBOOK'

REST_SERVER = 'http://api.facebook.com/restserver.php'

# You can get your User ID here: http://developers.facebook.com/tools.php?api
MY_FACEBOOK_UID = 'YOUR FACEBOOK User ID'

NOT_FRIEND_ERROR = 'You must be my Facebook friend to log in.'
PROBLEM_ERROR = 'There was a problem. Try again later.'
ACCOUNT_DISABLED_ERROR = 'Your account is not active.'
ACCOUNT_PROBLEM_ERROR = 'There is a problem with your account.'

class FacebookConnectMiddleware(object):
    
    delete_fb_cookies = False
    facebook_user_is_authenticated = False
    
    def process_request(self, request):
        try:
             # Set the facebook message to empty. This message can be used to dispaly info from the middleware on a Web page.
            request.facebook_message = None

            # Don't bother trying FB Connect login if the user is already logged in
            if not request.user.is_authenticated():
            
                # FB Connect will set a cookie with a key == FB App API Key if the user has been authenticated
                if API_KEY in request.COOKIES:

                    signature_hash = self.get_facebook_signature(request.COOKIES, True)
                
                    # The hash of the values in the cookie to make sure they're not forged
                    if(signature_hash == request.COOKIES[API_KEY]):
                
                        # If session hasn't expired
                        if(datetime.fromtimestamp(float(request.COOKIES[API_KEY+'_expires'])) > datetime.now()):

                            # Make a request to FB REST(like) API to see if current user is my friend
                            are_friends_params = {
                                'method':'Friends.areFriends',
                            	'api_key': API_KEY,
                            	'session_key': request.COOKIES[API_KEY + '_session_key'],
                            	'call_id': time.time(),
                            	'v': '1.0',
                            	'uids1': MY_FACEBOOK_UID,
                            	'uids2': request.COOKIES[API_KEY + '_user'],
                            	'format': 'json',
                            }
                
                            are_friends_hash = self.get_facebook_signature(are_friends_params)
    
                            are_friends_params['sig'] = are_friends_hash
                
                            are_friends_params = urllib.urlencode(are_friends_params)
                
                            are_friends_response  = simplejson.load(urllib.urlopen(REST_SERVER, are_friends_params))
                        
                            # If we are friends
                            if(are_friends_response[0]['are_friends'] is True):
                    
                                try:
                                    # Try to get Django account corresponding to friend
                                    # Authenticate then login (or display disabled error message)
                                    django_user = User.objects.get(username=request.COOKIES[API_KEY + '_user'])
                                    user = authenticate(username=request.COOKIES[API_KEY + '_user'], 
                                                        password=md5.new(request.COOKIES[API_KEY + '_user'] + settings.SECRET_KEY).hexdigest())
                                    if user is not None:
                                        if user.is_active:
                                            login(request, user)
                                            self.facebook_user_is_authenticated = True
                                        else:
                                            request.facebook_message = ACCOUNT_DISABLED_ERROR
                                            self.delete_fb_cookies = True
                                    else:
                                       request.facebook_message = ACCOUNT_PROBLEM_ERROR
                                       self.delete_fb_cookies = True
                                except User.DoesNotExist:
                                    # There is no Django account for this Facebook user.
                                    # Create one, then log the user in.
                    
                                    # Make request to FB API to get user's first and last name
                                    user_info_params = {
                                        'method': 'Users.getInfo',
                                    	'api_key': API_KEY,
                                    	'call_id': time.time(),
                                    	'v': '1.0',
                                    	'uids': request.COOKIES[API_KEY + '_user'],
                                    	'fields': 'first_name,last_name',
                                    	'format': 'json',
                                    }

                                    user_info_hash = self.get_facebook_signature(user_info_params)

                                    user_info_params['sig'] = user_info_hash
                    
                                    user_info_params = urllib.urlencode(user_info_params)

                                    user_info_response  = simplejson.load(urllib.urlopen(REST_SERVER, user_info_params))
                    
                    
                                    # Create user
                                    user = User.objects.create_user(request.COOKIES[API_KEY + '_user'], '', 
                                                                    md5.new(request.COOKIES[API_KEY + '_user'] + 
                                                                    settings.SECRET_KEY).hexdigest())
                                    user.first_name = user_info_response[0]['first_name']
                                    user.last_name = user_info_response[0]['last_name']
                                    user.save()
                    
                                    # Authenticate and log in (or display disabled error message)
                                    user = authenticate(username=request.COOKIES[API_KEY + '_user'], 
                                                        password=md5.new(request.COOKIES[API_KEY + '_user'] + settings.SECRET_KEY).hexdigest())
                                    if user is not None:
                                        if user.is_active:
                                            login(request, user)
                                            self.facebook_user_is_authenticated = True
                                        else:
                                            request.facebook_message = ACCOUNT_DISABLED_ERROR
                                            self.delete_fb_cookies = True
                                    else:
                                       request.facebook_message = ACCOUNT_PROBLEM_ERROR
                                       self.delete_fb_cookies = True
                            # Not my FB friend
                            else:
                                request.facebook_message = NOT_FRIEND_ERROR
                                self.delete_fb_cookies = True
                            
                        # Cookie session expired
                        else:
                            logout(request)
                            self.delete_fb_cookies = True
                        
                   # Cookie values don't match hash
                    else:
                        logout(request)
                        self.delete_fb_cookies = True
                    
            # Logged in
            else:
                # If FB Connect user
                if API_KEY in request.COOKIES:
                    # IP hash cookie set
                    if 'fb_ip' in request.COOKIES:
                        
                        try:
                            real_ip = request.META['HTTP_X_FORWARDED_FOR']
                        except KeyError:
                            real_ip = request.META['REMOTE_ADDR']
                        
                        # If IP hash cookie is NOT correct
                        if request.COOKIES['fb_ip'] != md5.new(real_ip + API_SECRET + settings.SECRET_KEY).hexdigest():
                             logout(request)
                             self.delete_fb_cookies = True
                    # FB Connect user without hash cookie set
                    else:
                        logout(request)
                        self.delete_fb_cookies = True
                        
        # Something else happened. Make sure user doesn't have site access until problem is fixed.
        except:
            request.facebook_message = PROBLEM_ERROR
            logout(request)
            self.delete_fb_cookies = True
        
    def process_response(self, request, response):        
        
        # Delete FB Connect cookies
        # FB Connect JavaScript may add them back, but this will ensure they're deleted if they should be
        if self.delete_fb_cookies is True:
            response.delete_cookie(API_KEY + '_user')
            response.delete_cookie(API_KEY + '_session_key')
            response.delete_cookie(API_KEY + '_expires')
            response.delete_cookie(API_KEY + '_ss')
            response.delete_cookie(API_KEY)
            response.delete_cookie('fbsetting_' + API_KEY)
    
        self.delete_fb_cookies = False
        
        if self.facebook_user_is_authenticated is True:
            try:
                real_ip = request.META['HTTP_X_FORWARDED_FOR']
            except KeyError:
                real_ip = request.META['REMOTE_ADDR']
            response.set_cookie('fb_ip', md5.new(real_ip + API_SECRET + settings.SECRET_KEY).hexdigest())
        
        # process_response() must always return a HttpResponse
        return response
                                
    # Generates signatures for FB requests/cookies
    def get_facebook_signature(self, values_dict, is_cookie_check=False):
        signature_keys = []
        for key in sorted(values_dict.keys()):
            if (is_cookie_check and key.startswith(API_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(API_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 + API_SECRET

        return md5.new(signature_string).hexdigest()
You will then need to tell Django to use this middleware. Add the following to MIDDLEWARE_CLASSES in settings.py:
'myproject.FacebookConnectMiddleware.FacebookConnectMiddleware',
At this point, users should be able to log into your Django-powered site using Facebook connect.


Displaying messages from the middleware (optional)

As you can see, the middleware also sets the
facebook_message
variable in the
HttpRequestObject
to pass error messages to templates. If you want to display these messages on a template, you'll need to update your view and the template. Add the following to your view:
if request.facebook_message is not None:
    facebook_message = request.facebook_message
else:
    facebook_message = ''
Then pass facebook_message to
render_to_response
. Then, in the template, add:
{% if facebook_message %}{{ facebook_message }}{% endif %}
You can update the middleware to change the default messages or to add new messages where desired.

Logging out

This link will log the user out of Facebook via JavaScript, then redirect them to the Django logout (assuming your logout url is configured to be /accounts/logout).
    <a id="logout" href="/accounts/logout" onclick='FB.Connect.ifUserConnected(null,function() { window.location = "/accounts/logout" }); FB.Connect.logoutAndRedirect("/accounts/logout"); return false;'>Sign out</a>



Comments

#1

ricky
December 15, 2008
9:45 p.m.

Just what I was looking for. Pretty awesome, thanks!

 
#2

omtv
December 17, 2008
10:04 a.m.

Yes, quite useful script! Thanks a lot.

 
#3

Srik
December 17, 2008
10:33 a.m.

Thanks, will try this out in next project.

 
#4

Daniel Nyström
December 17, 2008
12:24 p.m.

Why not just use “direct_to_template” in urls.py?

 
#5

Raisins
December 17, 2008
3:19 p.m.

Thanks for the middleware I was in the process of writing the same thing.

 
#6

Andrew
December 17, 2008
4:15 p.m.

You could also use the pyfacebook library to remove a lot of code.

 
#7

Julian
December 17, 2008
4:44 p.m.

Oh yeah, does pyfacebook have Facebook Connect?

 
#8

Bret
December 17, 2008
7:09 p.m.

@ Julian: Oh yeah, does pyfacebook have Facebook Connect?

I think Andrew was referring to my code that made calls to the Facebook API. If I’d used PyFacebook, I wouldn’t have needed to construct, make and parse the API interactions.

 
#9

Julian
December 18, 2008
11:03 a.m.

That’s cool then. Any chance we get some updated source code?

 
#10

Mark
February 4, 2009
7:55 p.m.

Bret Thanks for posting this snippet. This works great - even with a couple of my hacks. I did notice that to fully clear the cookies on logout - you have to logout of the Facebook account AND close the browser - or the middleware logs the user back in to the django session (not really sure it matters - but still… ). Any ideas how to unlink the fb cookie(s) from the django session without closing out the browser. I’m sure there is a way (after a bit of head scratching) but thought you might have run into this?

Thanks. Nice code.

 
#11

Bret
February 5, 2009
8:17 a.m.

I’ve added my logout link code to the end of the entry.

It logs the user out of Facebook via JavaScript, then redirects to Django’s logout page which logs the user out of Django.

 
#12

Jermaine
February 15, 2009
10:42 p.m.

Have not had a chance to test yet, but thanks so much for sharing. Almost finishing building a small app and wanted to use FBConnect. Love the idea that you can restrict login to users who are your friends as well.

 
#13

alen
February 19, 2009
6:25 a.m.

I guess you’re aware of it, but still the remark, there’s a django application that does this too. Check out http://code.google.com/p/django-fbcon... - I’ll update the repository the next few days so existing users can connect their account with their facebook account - and new users can actually pick a username. :)

Cheers, alen

 
#14

Danny
February 28, 2009
9:20 p.m.

My app uses Facebook Connect a little different. I created a Custom Authentication for it.

see http://blog.dannyroa.com/2009/02/24/custom-authentication-in-django-using-facebook-connect/

 
#15

ROb
March 27, 2009
12:03 a.m.

Hey,

I guess I’m late to the game here, I’ve been trying to utilize the middleware here. Something I want to mention is that the facebook tutorial now uses xd_receiver.htm, and no longer .html! So watch out when you set your URLS.PY to html$ you’ll never find it!

Secondly, the reason I am posting, is that I’ve been able to make the connection, I get a facebook cookie and I get my sites own cookie — but then it doesn’t make any sense as to how it knows what my login is.. shouldn’t it have created a new user account?

I’m checking my users table, and it didn’t make one, nor am I logged in, but my cookie is there never the less.

 
#16

Bret
March 30, 2009
9:30 p.m.

Rob,

This middleware should be creating a new user whose username is the same as your friend’s Facebook ID (e.g. 1233443).

I’m not sure why you wouldn’t see the user in your users table if you logged in via Facebook Connect.

Are you logging in as one of your friends, or are you logging in as yourself?

By default, this implementation only allows your friend to log in, and you can’t be your own friend on Facebook. So, if you’re logging in to Facebook Connect as yourself, maybe you’re able to access your admin section because you’re still logged in using your Django-based account.

If you’re able to get to the admin section, you’re logged in not with your Facebook account, but with your Django account.

Ask one of your friends to log in using FB Connect and see if they show up in the users table.

 
#17

Morgan
May 19, 2009
3:11 p.m.

I was playing with a php version to connect my app to facebook, but this seems pretty simple. I’m getting a lot of errors with php, and of course none of the error messages are helpful in php.

 
#18

Susan
June 12, 2009
7:27 p.m.

I’ve been looking for code that works with the Facebook API, connecting Facebook to some other CMS or software system. I will have to play around with this since I have not yet found a solution I am happy with (yet).

 
#19

jeff
July 7, 2009
2:55 a.m.

I installed everything per the instructions I believe and it all works except when I click the “sign out” link it doesn’t delete the cookie for facebook. So when a person signs out and then revisits they are still signed in with access. This is good if they are on a private computer, but they should be able to sign out and know that no one else will be able to come up behind them and gain access on a public computer. I checked and logging in through the django login method and logging out via accounts/logout does end the session correctly. I appears that only when a person logs in through the middleware does the session not expire. Any help would be lovely.

Thanks, I think this is a great option for people that want their friends and family to be able to login without creating a site specific login.

 
#20

Bret
July 7, 2009
9:14 a.m.

When you are signed in to Facebook and click the sign out link, does a Facebook modal window pop up letting you know that you’re being logged out of Facebook?

The JavaScript that’s included in the onclick event of the sign out link is what handles the Facebook cookie deletion. It also displays the modal, so if you’re not seeing the modal, it’s not working properly.

 
#21

jeff
July 8, 2009
9:19 p.m.

no I’m not seeing the popup window when I click the signout link. So I need to look into that.

 
#22

jeff
July 8, 2009
10:14 p.m.

Got it. I put the script calls inside an if statement incorrectly so that it wasn’t available for sign out. moved the script tags out of the if statement and works like a charm. Thank you so much. If a user of my site changes their username away from the facebook assigned number, will that break the link with their user profile and force the middleware to create a new user the next time they login?

Thanks again,

 
#23

Bret
July 9, 2009
8:49 a.m.

I don’t believe they can change their assigned number (their UID), so, no, it shouldn’t cause the middleware to create a new user.

 
#24

jeff
July 21, 2009
11:29 p.m.

After login the session begins, but the page doesn’t reload to show the person they are logged in. Is there a way to reload the page where they login using the button or redirect back to the homepage so they can see their login information appear on the page.

I have my pages set up to say “Welcome if they are logged in. I’d like them to see this information after a successful login.

Thanks, this is great.

 
#25

Bret
July 22, 2009
12:33 a.m.

Try: fb:login-button onlogin=”location.reload(true);”/fb:login-button

 
#26

jeff
July 22, 2009
6:16 p.m.

It appears that your your code example didn’t post. thanks for you help.

 
#27

jeff Lindsey
July 30, 2009
12:08 a.m.

Worked like a charm. thanks for your help.

 
#28

JR
October 4, 2009
6:04 a.m.

Thanks for the article! I have a problem though: i see the facebook connect button, but nothing happens when i click on it. (not even a javascript error).

Anyone have any experience with this?

thanks in advance!

 
#29

Bret
October 5, 2009
6:15 p.m.

JR,

No, unfortunately I don’t.

Are you sure you haven’t missed a step?

 
#30

JR
October 6, 2009
9:34 a.m.

Nope… in safari everything is working like it should… only firefox is giving me problems (haven’t dared to try IE… ;-) )

 
#31

Bret
October 6, 2009
1:25 p.m.

Is the page public? If so, send me the URL.

 
#32

Dasha Salo
November 25, 2009
7:41 a.m.

Great! thanks!

 
#33

Torsten
November 26, 2009
6:58 a.m.

Thanks for your work,

I have a problem though:

As soon as i activate the Middleware in the settings.py i couldn’t login anymore with existing users into the admin area. I get a message, my browser hasnt actived cookies.

Without the Middleware login into the admin area works.

Does anybody could help me?

 
#34

sybiam
January 16, 2010
12:36 a.m.

Hmm, I’m not exactly sure but I’m guessing there is a problem with your app. The password you use for new user is actually the user id and a secret.

And since the user id is constant and the secret is constant. If I was building my own facebook cookie with your facebook app key. I’m pretty sure I could get logged in. Unless there is a double check in with javascript.

I’m not sure how I’m supposed to trust those values and enable a create a user from which values. since anyone can add cookies and the userid they want.

And then you create a hash with it to log in. but I really only need to know the userid. (not really hard to do)

 
#35

Bret
January 16, 2010
11:24 p.m.

@sybiam Not so.

Look here:

 signature_hash = self.get_facebook_signature(request.COOKIES, True)

 # The hash of the values in the cookie to make sure they're not forged
 if(signature_hash == request.COOKIES[API_KEY]):

The get_facebook_signature method gets the MD5 signature of the values in the cookie combined with the API_SECRET (which you don’t know). This hash value is then compared with the signature value in the cookie. The signature value in the cookie was built by Facebook using my API_SECRET. Because both Facebook and my site know the API_SECRET they can build the hash of the parameters in the cookie combined with the API_SECRET. This hash value can be passed in the open (as can all of the Facebook cookie parameters) because modifying it or any of the parameters would mean that when my code checks the hash, something won’t add up, and you won’t be allowed to log in.

If I didn’t do this check of the hash, you’re right, you’d be able to log in by changing the cookie.

 
#36

Alex
January 18, 2010
7:02 p.m.

I am trying to make it work on google app engine and i receive this error “default new takes no parameters” in the middleware class and now i don’t know if i am doin something wrong or is it the google app engine that makes the difference.

Has anybody tried to make it work on the google app engine?

 
#37

Red
January 22, 2010
11:33 a.m.

After setting up and clicking on the connect button, why is the popup window not closing, plus the new page opens up in the popup instead of the origin window.

 
#38

Facebook Connect
March 15, 2010
5:27 a.m.

Thanks for this post, its really helpfull

 
#39

marman
June 10, 2010
5:39 p.m.

Hi, im having problems importing simplejson in python 2.6

ImproperlyConfigured: Error importing middleware Myproject.FacebookConnectMiddleware: “No module named simplejson”

 

Post a comment

Markdown accepted here.
You can also post links to YouTube, Vimeo, etc., and your links will turn into embedded media!

Your e-mail address will not be shown publicly.