Many applications require the use of password based authentication. In many web applications, this is done using cookies/sessions, and the login mechanism is an HTML form that is submitted to the application. If you are trying to build a RESTful web service, or any service where this client may be automated, It becomes useful to utilize HTTP authentication.
It may not be immediately obvious from the CherryPy documentation, but CherryPy includes all the tools needed to implement HTTP authentication, both Digest and Basic. You can see what the CherryPy website has documented about authentication at the CherryPy Builtin Tools Page. The best documentation for this is available in the book: CherryPy Essentials: Rapid Python Web Application Development
Basic vs Digest
HTTP supports two types of authentication, basic and digest. To a user accessing a protected page through a web browser, both types look the same. The browser will prompt the user for a username and password. The difference is how the client (browser) sends the password to the server.
With basic authentication, the client sends the password in clear-text, so any malicious attacker who can see the request can see the password. With digest authentication, the client does not send the password, but rather a digest based on the password and other factors. The server will also compute the digest using the known correct password. If the digest sent by the client matches the one calculated by the server, authentication succeeds.
The catch with digest authentication is the users’ passwords must be stored stored in clear-text on the server** in order to calculate the digest. On the flipside, basic authentication must send the password in clear-text through the network. If you are going to use basic authentication, it is recommended you use SSL to prevent the password from being sniffed.
** This is not entirely true, The digest algorithm allows you to store a partially digested password on the server side, however CherryPy has no easy way to specify this.
Digest Authentication
The following code creates a simple application with a public page and a secure page using digest authentication. The server configuration defines that the /secure url is to be protected with digest authentication (via the tools.digest_auth Tool available in CherryPy)
import cherrypy
class RootServer:
@cherrypy.expose
def index(self):
return """This is a public page!"""
class SecureServer:
@cherrypy.expose
def index(self):
return "This is a secure section"
if __name__ == '__main__':
users = {"admin": "secretPassword",
"editor": "otherPassword"}
conf = {'/secure': {'tools.digest_auth.on': True,
'tools.digest_auth.realm': 'Some site',
'tools.digest_auth.users': users}}
root = RootServer()
root.secure = SecureServer()
cherrypy.quickstart(root, '/', config=conf)
Run this code and visit http://localhost:8080/ . Now visit http://localhost:8080/secure. When your browser prompts you to do so, enter “admin” and “secretPassword” for the username and password respectively.
The /secure configuration section sets up the digest authentication with the following options:
- ‘tools.digest_auth.on’: This boolean enables digest authentication for the given section (in this case /secure)
- ‘tools.digest_auth.realm’: This string defines the realm to supply to the client.
- ‘tools.digest_auth.users’: This dictionary defines users and passwords ({user: password}). This can also take two other forms, please see below.
There is a bug in the CherryPy Digest Authentication tool that prevents authentication when the request is a post request with a body. See Cherrypy Digest Auth POST Problem (and Solution!)
Basic Authentication
We are going to use the same code to implement basic authentication, but we will change the configuration variables.
import cherrypy
class RootServer:
@cherrypy.expose
def index(self):
return """This is a public page!"""
class SecureServer:
@cherrypy.expose
def index(self):
return "This is a secure section"
if __name__ == '__main__':
users = {"admin": "secretPassword",
"editor": "otherPassword"}
def clear_text(passwd):
return passwd
conf = {'/secure': {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'Some site2',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}}
root = RootServer()
root.secure = SecureServer()
cherrypy.quickstart(root, '/', config=conf)
Run this code as before, and supply the same username and password. Can’t tell the difference? Remember, the difference is in how it is transmitted. Here are the options we supplied to enable basic authentication:
- ‘tools.basic_auth.on’: This boolean enables digest authentication for the given section (in this case /secure)
- ‘tools.basic_auth.realm’: This string defines the realm to supply to the client.
- ‘tools.basic_auth.users’: This dictionary defines users and passwords ({user: password}). This can also take two other forms, please see below.
- ‘tools.basic_auth.encrypt’: This takes a function that encrypts the user-supplied password to match a pre-encrypted password in the user dict. (In our case, the function clear_text we defined just passes the password through, allowing us to store password in clear-text for ease of demonstration). If you do not supply this option, it will be assumed the passwords are stored in MD5.
Defining Users
You can supply either a dict or function to the ‘tools.******_auth.users’ option to define your users. There are three ways to supply users: as a dict ( {user:password} ), as a function that returns a dict ( {user:password} ), or as a function that takes a username as an argument, and returns a password.
Remember that password could mean the clear-text password, or an encrypted password, depending on whether your using basic or digest, and the encryption option specified.
Using a dictionary
Simply supply a dictionary of {username: password} to the users option of the authentication configuration.
users = {'user1': 'password1',
'user2': 'password2}
conf = {'/secure': {'tools.digest_auth.on': True,
'tools.digest_auth.realm': 'Some site',
'tools.digest_auth.users': users}}
Using a function that returns a dictionary
Supply a function that takes no arguments and returns a dictionary containing all usernames and passwords
def get_users():
return {'user1': 'password1',
'user2': 'password2}
conf = {'/secure': {'tools.digest_auth.on': True,
'tools.digest_auth.realm': 'Some site',
'tools.digest_auth.users': get_users}}
Using a Function Which Takes a Username (Most Useful)
Many times, applications have so many users that it does not make sense to pass the list of all users around like the options above. I have not seen this feature documented anywhere, but the code allows it. You can specify a function that takes a username string as an argument, and returns the password for that user. This function could consult a database or other mechanism, and allows your application to add and remove user more easily.
def find_password(username):
#INSERT PASSWORD LOOKUP LOGIC HERE
password = db.find_password_for_user(username)
return password
conf = {'/secure': {'tools.digest_auth.on': True,
'tools.digest_auth.realm': 'Some site',
'tools.digest_auth.users': find_password}}
Retrieving the Username
Once authenticated your application will likely want to know which user is logged in. The username of the user who has successfully authenticated will be available at ‘cherrypy.request.login’. This variable wil be False if authentication failed.
In Closing
It may not be well documented, but the tools for basic and digest authentication are already built in to CherryPy. You just have to know how to use them, and now you do.
If there are any problems with the code or this post in general, leave me a comment and I will make it right.
