Web.py Tutorial – Facebook Authentication

This is a rehash of the old tutorial after the update in the documentation.

Facebook is one of the most widely used authentication system, used especially to reduce the hassle of signing up for a new application. In this tutorial, I will be showing one way to integrating facebook authentication for your typical application.

This tutorial makes use of Facebook’s Graph API instead of the different SDKs. This method will work as long as your client is able to connect to the internet to access the APIs, be it Android, iOS or other platforms.

There are 2 phases to Facebook’s authentication system — creating a session to request an access token, and requesting for an access token. In the first part, a code is return, it is then used to request an access token. The token is used to access the information using Facebook’s Graph’s API.

For the first part, you will redirect the user to the following page: https://www.facebook.com/dialog/oauth using the client_id and redirect_uri.

### Initiate first stage of the process
if not i.get('code'):
   raise web.seeother('https://www.facebook.com/dialog/oauth?client_id='+client_id+'&redirect_uri='+uri)

In the second part, the access_token will be requested from https://graph.facebook.com/v2.3/oauth/access_token. The client_id and client_secret, together with the code will be used to generate the access_token. Because details of the application are required for this process, it is more to do it through the server. For that, we will make use of the python requests library.

r = requests.get(fb_graph+'oauth/access_token', params={
    'client_id':client_id,
    'client_secret':client_secret,
    'code':i.get('code'),
    'redirect_uri':uri
}).json()

To test if the authentication actually works, we redirect to a site to test the Facebook Graph API. This is where we get the basic profile information about the user and the profile picture.

r = requests.get(fb_graph+'me', params={'access_token':i.get('access_token')}).json()

return "<html><h3>%s (%s)</h3><img src='%s' /><br/><a href='%s'>View Profile</a></html>"%(r['name'], r['gender'], fb_graph+r['id']+'/picture', r['link'])

For the full web.py application, with the structure of the different pages, visit the gist below.

Advertisements

Web.py Application – FB Authentication

EDIT: There are some updates to the FB documentation, it doesn’t affect the login process a lot, but I decided to rehash this tutorial anyway. Updated tutorial

Access to Github Repository

Introduction

Facebook authentication is one of the easiest way of authenticating your user without them having to create a full-fledged user profile system. Here is a quick tutorial of how to integrate facebook login with your web.py application.

Here is the basic setup of the application:

import web
import json
import time
import urllib
import urlparse

FB_APP_SECRET = ''
FB_APP_ID = ''

urls = [
'/', 'Index',
'/li', 'Login',
'/lo', 'Logout'
]
app = web.application(urls, globals())

def getURL():
return web.ctx.home + web.ctx.fullpath

Logging In

The login stage of the application requires two main stages: the authentication stage and the retrieval of the access token.

In the authentication stage, the user is redirected to http://www.facebook.com/dialog/oauth where they will accept all the permissions that the application will require. What is returned will be the code which will be used to retrieve the access token for that particular session.

In the second stage, the user is redirected to https://graph.facebook.com/oauth/access_token for the access token. The result is read using the parse_qs function from urlparse, where the access token is extracted.

The last stage of the login will be extracting the user information for storage in the cookies. Using the access token, the user’s profile information is retrieved from https://graph.facebook.com/me, where the user id and the user name is stored for display.

class Login:
def GET(self):
i = web.input(code = None)
args = dict(client_id=FB_APP_ID, redirect_uri=getURL())

### Authentication stage
if not i.code:
web.seeother('http://www.facebook.com/dialog/oauth?' + urllib.urlencode(args))
return

### Access token stage
args['code'] = i.code
args['client_secret'] = FB_APP_SECRET
req = 'https://graph.facebook.com/oauth/access_token?' + urllib.urlencode(args)
res = urlparse.parse_qs(urllib.urlopen(req).read())
tkn = res['access_token'][-1]

### Retrieving profile information
req = 'https://graph.facebook.com/me?' + urllib.urlencode(dict(access_token=tkn))
res = json.load(urllib.urlopen(req))

t = time.time() + 7*86400
web.setcookie('fb_uid', res['id'], t)
web.setcookie('fb_uname', res['name'], t)

web.seeother('/')

Logging Out

Since we are using cookies to store the session details, logging out will just require us to reset the cookies.

class Logout:
def GET(self):
web.setcookie('fb_uid', '', time.time() - 86400)
web.setcookie('fb_uname', '', time.time() - 86400)
web.seeother('/')

Displaying the information

After the user have logged in, some of the user information will be stored in the cookies. We will make use of this by displaying the user’s name and profile picture if he is logged in. We do so by checking the state of the cookie variables fb_uid and fb_uname, if they exist, they will be printed out, else, a login link will be displayed instead.

class Index:
def GET(self):
html = "<a href="/li">FB Login</a>"

uid = web.cookies().get('fb_uid')
uname = web.cookies().get('fb_uname')

if uid and uname:
html = "<img src="https://graph.facebook.com/"+uid+"/picture" alt="" />
<h4>"+uname+"</h4>
<a href="/lo">Logout</a>"

return '''
<h1>Hi!!!</h1>
'''+html+'''


'''

Conclusion

There you have it, your own web.py application with facebook authentication.

Things to note

  • To integrate a more general form of OAuth system, take a look at the python-oauth2 package
  • Never reveal your app secret and id, you never know who is looking
  • Original inspiration

Access to Github Repository

Web.py Application – User Auth with token features

Visit Github Repository to view the code sample in full.

Introduction

User authentication is one of the most basic system that all web application will have. For many frameworks, there is already some sort of authentication system you could use, but for web.py, there is not such system available. But it shouldn’t stop you from using the framework, because creating one is not really that difficult.

In the past, I have written a tutorial on creating login systems for web application, but when I am working on a data API, I realized that having a token system makes work much much easier. You could integrate other authentication standards using this code, but this is just the very basic framework that you build upon for your application.

Differences from last version

In the last version of the tutorial, I used sessions to store the user’s session, but for this version, I will be designing it for a RESTful API, so, the bulk of the authentication will be done through the generation and usage of tokens.

Creating the database

This is pretty self explanatory, but this is just the most basic of the database structure, alter to fit your own needs.

app = web.application(urls, globals())
db = web.database(dbn='sqlite', db='database.db')
db.query('''
    CREATE TABLE IF NOT EXISTS users(
        uid INTEGER PRIMARY KEY,
        uusr TEXT NOT NULL,
        upwd TEXT NOT NULL,
        utme INTEGER,
        utkn TEXT
    );
''')

Additional functions

These are functions that are required for the user authentication system to work, accomplishing all the backend task such as accessing the database or generating tokens.

Generating tokens

This is a simple token generating scheme that you could use. For your application, you could use a much more sophisticated scheme.

def genToken():
    r = rand.random()
    return str(hashlib.sha1('%f%s'%(r, time.ctime())).hexdigest())

Encoding the password

It is never secure to save your password as plaintext. Instead use password hash scheme such as the one implemented here.

def getPwd(usr, pwd):
    return hashlib.sha1('%s:%s'%(usr, pwd)).hexdigest()

Signing up for a new account

The section of the code ensures that the request is in the correct format and if everything is all and well, it will serve to create an user account and store the user information into the database. The most crucial error that this process is the possibility that the user account might have already been created before, hence, we got to handle that error right there.

class SignUp:
    @json_response
    def POST(self):
        i = web.input()
        if not i.get('usr') or not i.get('pwd'):
            return web.badrequest()
        usr = i.get('usr');
        pwd = getPwd(usr, i.get('pwd'))

        if getUser(usr):
            return {'error': 'userExist - user already exist'}

        db.insert('users', uusr=usr, upwd=pwd, utkn=genToken(), utme=int(time.time()))

        return {'sucess': True}

Signing in and getting a token

Signing into the data API would return a access token, but if the token is not accessed within 4 hours of the last use, the token is regenerated and the new token will be returned. This is just a simple scheme that is implemented here, it should be changed to suit your application’s needs.

There are a few errors that might occur during the sign in process. For one, the user account might not already exist. The password could be wrong, authentication information might be missing.

class SignIn:
    @json_response
    def GET(self):
        i = web.input()
        if not i.get('usr') or not i.get('pwd'):
            return web.badrequest()
        usr = i.get('usr')
        pwd = getPwd(usr, i.get('pwd'))

        user = getUser(usr)
        if not user:
            return {'error': 'userNotExist - user does not exist'}

        if not user['upwd']==pwd:
            return {'error': 'invalidPass - invalid password, try again'}
        else:
            t = int(time.time())
            ### Token expires within 4 hours of inactivity
            if user['utkn'] && t-user['utme'] > 14400:
                token = genToken()
            else:
                token = user['utkn']

            res = db.update('users', where='uid=$id', utkn=token, utme=int(time.time()), vars={'id':user['uid']})

            return {'success':True, 'token':token}

        return {'error':'loginError - cannot login'}

Signing out

For a token access system, the signing out process will just be removing the token and resetting the sign in time. And by resetting the token, the previous token will be invalidated and it will be just like signing the user out of the system.

class SignOut:
    @json_response
    def GET(self):

        i = web.input()
        if not i.get('tkn'):
            return web.badrequest()
        tkn = i.get('tkn')

        user = getUser(None, tkn)
        if not user:
            return {'error':'Not logged in'}

        res = db.update('users', where='uid=$id', utkn=None, vars={'id':user['uid']})

        return web.seeother('/')

Things to note

  • For testing purposes, the functions corresponds to GET request, but for product do change them to POST queries that do not make plaintext information as available
  • User authentication system should always be done using some sort of security, namely with a SSL enabled connection
  • This set of of code is just a sample proof of concept. Always alter the code to suit your own needs.
  • If you have any questions or if anything is unclear, shoot me an email.

Tutorial – Uploading Web.py Projects To Heroku

Setup The Environment

So first, we have to setup the environment for heroku server. First, we nid to tell the server what python libraries to download and we can easily do that with pip, with the freeze function to get all the dependencies downloaded. Mainly, this will include web.py and whatever dependencies you have downloaded for your project.

pip freeze > requirements.txt

This saves all the dependencies into requirements.txt, a file which heroku will use to tell what to download.

Procfile

The Profile consist of all the dynos that will be requried to run your application. The most basic of all application uses one dyno only, which is the web. This dyno allows your app to run and liten to HTTP request.

web: python main.py $PORT

The variable $PORT, is a envrionment variable defined by Heroku and will result in the application attaching to the pre-defined port number.

Pushing to Heroku

Heroku uses git to manage its project. And you can copy the git link by going to your project dashboard and put the link into your project as heroku.

Copy GIT Link

git remote add heroku <link>

After that you just git push heroku master to push your project online. You will see Heroku setting up your application, downloading all the neccessary dependencies and running the dynos.

In case of error

If you receive any error about not being able to authenticate with Heroku, visit my tutorial here to learn how to setup your Git with heroku.

Tutorial – Long Polling with web.py

All about long polling (Extra Reading)

Push technology, is a style of internet based communication where the request for a given transaction is initiated by the server, rather than the client. This is especially useful for applications that require real time updates.

Long polling is not a true push, but given the restricted web environment where incoming HTTP requests are restricted, it emulates the push function sufficiently well.

With long poll, the client requests information in exactly the same way. If the server does not have the information required, instead of sending an empty response, the server holds the request until the information becomes available. Once the response is sent to the client, another request is fired again until the server returns with the information requested.

Live Messageboard, or (what we will be doing)

The application will be done with web.py in the back end, serving the web pages for index / and the message submission page /add, alongside responses to http request /get for new updates.

The messages submitted will be stored on a sqlite database, chosen for its simplicity and size. The request send to /get will include a timestamp, whereby only messages sent after that timestamp will be return.

The JSON response will then be received by the client, after the page has been updated, another XHR will be fired to the server, awaiting new updates for the message board

Frontend request firing script

So, in this tutorial, we will be using a standard XMLHttpRequest to send request to the server to obtain any new updates on the message board. creating a generic function startConn function which passes the JSON information sent from the server into a callback function, then sends another request to the server using the link returned from the callback function.

Save all these into static/conn.js:

function startConn(link, callback){
    var conn = new XMLHttpRequest();
    conn.open("GET", link);
    conn.onreadystatechange = function(){
        if(conn.readyState==4){
            var link_new = callback(eval("("+conn.responseText+")"));
            if(!link_new){ 
                link_new = link;
            }

            startConn(link_new, callback);
        }
    };
    conn.send();
    return conn;
}

Setup Database

Using sqlite3, create a table called messages with a few fields, mainly the message content msg_content and the timestamp, for retrieval purposes, msg_time.

CREATE TABLE messages(
    msg_id INTEGER PRIMARY KEY,
    msg_content TEXT NOT NULL,
    msg_time INTEGER NOT NULL
);

I have saved the database in data.db (you could choose your own name). So, we can start on the main server file main.py:

import web, time, json
render = web.template.render("")

urls = [
    "/", "Index"
]

class Index:
    def GET(self):
        return "<h1>Hello World!</h1>"

app = web.application(urls, globals())
db = web.database(dbn="sqlite", db="data.db")

if __name__ == "__main__":
    app.run()

This creates a web.py app, which is run on port 8080 (by default). In any browser, go to localhost:8080 and you will see a header welcoming you into the world.

Adding Messages

Next we will work on the interface for submitting messages to the server. This will just be a simple form where there is a textarea for typing messages and a simple submit button. Create this file as form.html under the same directory as the main server file.

<html>
    <body>
        <form action="/add" method="POST">
            <textarea type="text" name="s" style="width:500px; height:400px;"></textarea>
            <input type="submit" value="Send It In" />
        </form>
    </body>
</html>

For the main server script, we will add a few lines to serve this form.html when users visit the page /add, then we will also add a POST function so as to retrieve the message content and then put it into our database.

import web, time, json
render = web.template.render("")

urls = [
    "/", "Index",
    "/add", "MsgAdd"
]

class Index:
    def GET(self):
        return "<h1>Hello World!</h1>"

class MsgAdd:
    def POST(self):
        ## Check if the content is empty
        s = web.input().get("s")
        if not s:
            web.seeother("/add")

        ## Insert the message into the database
        db.insert("messages", 
            msg_time = str(int(time.time()*1000)),
            msg_content = s
        )

        web.seeother("/add")

    def GET(self):
        ## Show contents of form.html
        return render.form()

app = web.application(urls, globals())
db = web.database(dbn="sqlite", db="data.db")

if __name__ == "__main__":
    app.run()

The Messageboard itself

The main page, when first visited will show all the messages, then connects to the server to see if there is any new messages to load. To do this, we make use of web.py’s templating system to create a index.html, and dynamically load all the message content onto the website when it loads.

$def with (msgs)

<html>
    <head>
        <title>Message Board</title>
        <script src="static/conn.js"></script>
    </head>
    <style>
body, div{
    margin:0;
    padding:0;
}

.msg{
    padding:5px 8px;
    border-bottom:1px solid #000000;
    cursor:pointer;
}

.msg:hover{
    background:#DDD;
}
    </style>
    <body>
        <div id="main">
            $for msg in msgs:
                <div class="msg">
                    $msg["content"]<br />
                    <div style="text-align:right;">$msg["time"]</div>
                </div>
        </div>
    </body>
</html>

To retrieve the message content, we write a function inside main.py to retrieve the content and then parse it into an object:

def loadMsgs(msgs):
    payload = []
    for msg in msgs:
        payload.append({
            "content":msg["msg_content"],
            "time":time.strftime("%Y-%m-%d %H:%m:%S %p", time.gmtime(msg["msg_time"]/1000))
        })

    return payload

Then to serve this page, we now make a few edits to the Index class of the server script:

class Index:
    def GET(self):
        msgs = db.select("messages")
        return render.index(loadMsgs(msgs)[::-1])

Getting new messages

To retrieve new messages, a request is fired to the server and it searches through the database for new messages. And here is the important part, when the server sees that there is no new message, it simplys wait and search again later. To relieve load, a staggering effect is used, where with each failure, the staggering time is increased, until a valid response is received This is how the long polling is achieved.

urls = [
    "/", "Index",
    "/get", "MsgGet",
    "/add", "MsgAdd"
]

class MsgGet:
    def GET(self):
        t = web.input().get("t")
        if not t:
            raise web.notfound()

        msgs = []
        t_slp = 1
        t_add = 1
        t_max = 20
        while not len(msgs):
            if type(msgs) != "list":
                time.sleep(t_slp)
                t_slp = min(t_slp+t_add, t_max)

            msgs = db.select("messages", where="msg_time>"+t)
            msgs = [dict(msg) for msg in msgs]

        return json.dumps({
            "msgs":loadMsgs(msgs)
        })

Now we create a callback function to handle the request send back by the server and put it inside static/main.js, remembering to add it to index.html:

function loadMsgs(obj){
    // Format the information into html and add onto the page
    var html = "";
    for(var i=0; i<obj["msgs"].length; i++){
        var msg = obj["msgs"][i];
        var div = '<div class="msg">' +
            msg["content"] + '<br />' +
            '<div style="text-align:right;">' +
            msg["time"] + '</div></div>';
        html += div;
    }
    var old = document.getElementById("main").innerHTML;
    document.getElementById("main").innerHTML = html + old;

    // return an updated link with the current time
    return "/get?t=" + d.getTime().toString();
}

When the page starts, we will trigger the connection using the startConn() function that we have written in conn.js by editing the onload function:

<body onload="var d = new Date(); startConn('/get?t='+d.getTime().toString(), loadMsgs);">

Improvements to make, places to go

Well, with everything done, save all the files, then run the server file. Add a message on localhost:8080/add and then watch the index page refreshes and updates all the messages automatically.

However, the limited webserver that web.py uses means that it is unlikely that it is capable of supporting multiple long poll request at a time. So next time, I will be teaching you how to setup web.py with lighttpd to handle these request smoothly.