Are you looking for my non-technical blog?

This is now my technical-only blog, my non-technical blog is here.

09 October 2009

CherryPy Custom Authentication

You can read a better formatted version of this post here.

While working on Baralbait's API - yes, we may have an API someday, and you can sure contact us if you need to know more about it. Anyway, while developing the API, we were planning to use CherryPy Basic Authentication, in order to authenticate the API Calls.

This is how to add CherryPy Basic Authentication to one of your methods/pages:
import cherrypy

users = {"user1": "secret1", "user2": "secret2"}

def clear_text(mypass):
    return mypass
 
class MyServer:

    def index(self):
        return "This is a public page!"
    index.exposed = True

    def secret(self)
        print "Logged user is:", cherrypy.request.login
        return "Awesome, you've just accessed a page that requires HTTP Basic Authentication"
    secret.exposed = True
    secret._cp_config = {'tools.basic_auth.on': True,
        'tools.basic_auth.realm': 'My Secure Server',
        'tools.basic_auth.users': users,
        'tools.basic_auth.encrypt': clear_text}
 
if __name__ == '__main__':

    cherrypy.quickstart(MyServer(),"/","myserver.conf")

The above code means, that the page called "index", is a public page, while "secret" requires HTTP Basic Authentication, with the credentials mentioned in the "users" dictionary. For more info, Jim Hoskins has an awesome tutorial about using HTTP Basic Authentication in CherryPy, however his site is down now :(

Now, the problem with the above code, is that you can either make a certain page public or secured, but you cannot make it public and private in the same time. Ok, please be patient, let's say that you want authenticated users to see certain content when visiting our secret page, while unauthenticated users should see different content instead of being blocked. For example, we want authenticated users to see their friend's news feed, while unauthenticated users see public news.

So, here comes the beauty of CherryPy Custom tools. You can now, build your own authentication hook, and make it return a null or custom user id when an incorrect or no username or password are given, instead of totally blocking the user.

And here are the modifications needed to the above code:
import cherrypy

from cherrypy.lib import httpauth

users = {"user1": "secret1", "user2": "secret2"}

def clear_text(mypass):
    return mypass

def my_basic_auth(realm, users, encrypt=None):
    if cherrypy.lib.auth.check_auth(users, encrypt):
        print "DEBUG: Authenticated"
        return
    else:
        print "DEBUG: Not Authenticated"
        cherrypy.request.login = "Anonymous"
        return
 
class MyServer:

    def index(self):
        return "This is a public page!"
    index.exposed = True

    def secret(self)
        if cherrypy.request.login == "Anonymous":
            return "This is another public page on our useless website."
        else:
            return "Can you keep a secret, this page is really confidential."
    secret.exposed = True
    secret._cp_config = {'tools.my_basic_auth.on': True,
        'tools.my_basic_auth.realm': 'My Secure Server',
        'tools.my_basic_auth.users': users,
        'tools.my_basic_auth.encrypt': clear_text}
 
if __name__ == '__main__':

    cherrypy.tools.mybasic_auth = cherrypy.Tool('on_start_resource', my_basic_auth)
    cherrypy.quickstart(MyServer(),"/","myserver.conf")

So, we have just created a custom tool, and hooked it in the earliest hook ever, 'on_start_resource', i.e. during the request. We also created our own authentication method, 'my_basic_auth', and attached it to the tool. In our authentication method, which is almost identical to CherryPy's built in HTTP Basic Authentication method, however we do not raise any errors regardless the user is connected or not, we just set 'cherrypy.request.login' to an arbitrary user, that our application can understand later on, such as 'Anonymous'.