I have a CherryPy web application that requires authentication. I have working HTTP Basic Authentication with a configuration that looks like this:
app_config = {
'/' : {
'tools.sessions.on': True,
'tools.sessions.name': 'zknsrv',
'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'zknsrv',
'tools.auth_basic.checkpassword': checkpassword,
}
}
HTTP auth works great at this point. For example, this will give me the successful login message that I defined inside AuthTest:
curl http://realuser:realpass@localhost/AuthTest/
Since sessions are on, I can save cookies and examine the one that CherryPy sets:
curl --cookie-jar cookie.jar http://realuser:realpass@localhost/AuthTest/
The cookie.jar file will end up looking like this:
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This file was generated by libcurl! Edit at your own risk.
localhost FALSE / FALSE 1348640978 zknsrv 821aaad0ba34fd51f77b2452c7ae3c182237deb3
However, I’ll get an HTTP 401 Not Authorized failure if I provide this session ID without the username and password, like this:
curl --cookie 'zknsrv=821aaad0ba34fd51f77b2452c7ae3c182237deb3' http://localhost/AuthTest
What am I missing?
Thanks very much for any help.
So, the short answer is you can do this, but you have to write your own CherryPy tool (a
before_handler), and you must not enable Basic Authentication in the CherryPy config (that is, you shouldn’t do anything liketools.auth.onortools.auth.basic...etc) – you have to handle HTTP Basic Authentication yourself. The reason for this is that the built-in Basic Authentication stuff is apparently pretty primitive. If you protect something by enabling Basic Authentication like I did above, it will do that authentication check before it checks the session, and your cookies will do nothing.My solution, in prose
Fortunately, even though CherryPy doesn’t have a way to do both built-in, you can still use its built-in session code. You still have to write your own code for handling the Basic Authentication part, but in total this is not so bad, and using the session code is a big win because writing a custom session manager is a good way to introduce security bugs into your webapp.
I ended up being able to take a lot of things from a page on the CherryPy wiki called Simple authentication and access restrictions helpers. That code uses CP sessions, but rather than Basic Auth it uses a special page with a login form that submits
?username=USERNAME&password=PASSWORD. What I did is basically nothing more than changing the providedcheck_authfunction from using the special login page to using the HTTP auth headers.In general, you need a function you can add as a CherryPy tool – specifically a
before_handler. (In the original code, this function was calledcheck_auth(), but I renamed it toprotect().) This function first tries to see if the cookies contain a (valid) session ID, and if that fails, it tries to see if there is HTTP auth information in the headers.You then need a way to require authentication for a given page; I do this with
require(), plus some conditions, which are just callables that returnTrue. In my case, those conditions arezkn_admin(), anduser_is()functions; if you have more complex needs, you might want to also look atmember_of(),any_of(), andall_of()from the original code.If you do it like that, you already have a way to log in – you just submit a valid session cookie or HTTPBA credentials to any URL you protect with the
@require()decorator. All you need now is a way to log out.(The original code instead has an
AuthControllerclass which containslogin()andlogout(), and you can use the wholeAuthControllerobject in your HTTP document tree by just puttingauth = AuthController()inside your CherryPy root class, and get to it with a URL of e.g. http://example.com/auth/login and http://example.com/auth/logout. My code doesn’t use an authcontroller object, just a few functions.)Some notes about my code
user_verify()anduser_is_admin()debugprint()function which only prints output when aDEBUGvariable is set, and I’ve left these calls in for clarity.cherrypy.tools.WHATEVER(see the last line); I called itzkauthbased on the name of my app. Take care NOT to call itauth, or the name of any other built-in tool, though .cherrypy.tools.WHATEVERin your CherryPy configuration.My solution, in code
Now all you have to do is enable both builtin sessions and your own
cherrypy.tools.WHATEVERin your CherryPy configuration. Again, take care not to enablecherrypy.tools.auth. My configuration ended up looking like this: