Web.py Application - User Auth with token features

tutorial python webdev webpy

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.