Ian Bicking: the old part of his blog

Re: More on single-signon

Are you trying to do authentication (checking that someone is who they say they are) or authorization (checking if user x is allowed to look at this) or both? If you're just trying to get the authentication managed in a single place, authen.py looks most of the way there to me (although it is hugely open to replay attacks unless you're changing the secret every hour or so; I'd add an expiry time and IP to the cookie and sign them with the secret too). If you're doing authorization, the implementation in authen.py doesn't seem the greatest, since you probably want more complicated rules. So where do you want to write the rules? Does each page/application manage its own authorization (assuming it can get an authenticated username), or do you want to store the authorization rules in apache's config file ("require valid-user"), or somewhere else entirely (like a database)?

When I needed SSO for Apache2, I did pretty much the same thing as your authen.py, except I made it as an authen handler instead of a header parser and used req.requires() to let me be a bit more Apache-like in the config file (I also had an analogue of groups in my login system):

PythonAuthenHandler mysso
AuthType foobar # I recall having an AuthType (even a made up one) was necessary for anything to happen
Require valid-user
or Require user bob
or Require group payingCustomers

So, the script handled both authentication and authorization, although the actual authorization rules were contained in Apache's config/.htaccess files. All the handler had to do was check that the cookie contained valid info (expiry time hadn't passed and wasn't too far in the future, user's ip was the same as the cookie-contained ip, and hash(username+group+expiration+ip+secret) matched the signature) and only shoved the username into req.user if it was valid. If req.requires() returned anything, it would also do authorization and check that whatever req.requires returned was true (groups matched or the username matched or in the case of valid_user, the username had been set).

As to handling a lack of valid authorization, I just used a straight-up 307 Redirect, ignoring the 401 code -- if you aren't using http-based authentication, there's no point in sending 401. When a user wasn't logged in, it would redirect (returning apache.HTTP_TEMPORARY_REDIRECT) to the login page like https://SecureLoginSite.InSameDomain/login?from=protectedPagesURL. Obviously, the login form would redirect back to protectedPagesURL after the user logged in and got the (domain-wide) login cookie. If everything was okay, I set req.user to username (which seemed to be all I ever needed; I'm not sure what advantage writing a fake Authorization header gets you, unless it's part of some further authorization step) and returned apache.OK, which let the page load normally.

The only problems with it were that it didn't gracefully handle POSTs to its protected area (ideally it would encode the POSTed data into the login URL and send a 303 See Other to redirect to the login URL, and when the login URL redirected back to it, rewrite the request into a POST with the encoded data) and presumably it didn't perform overly well, but that was never too important to me (it didn't have a great deal of load on it; heck, I was getting away with opening a file and reading the secret key from it on every request!) I also didn't like the way that everything was in trusted cookies which could be stolen, but if you restrict the expiry time to something resonable and track the IP it is at least somewhat safe.

So, not general-purpose or easily dropped-in (since it required a custom login app which pulls up the groups the user belongs to, checks the password is right, creates a cookie, redirects, etc.), but it worked for me. Ideally, of course, you'd use Kerberos as an HTTP authentication method, but good luck finding browser support for that, and I suppose the user would have to remember to login as username@yourwebsite.com which might be a bit unintuitive.

Comment on More on single-signon

Comments:

Hmm... well, authentication and authorization are always hard for me to keep track of. Lets call it identification and authorization.

The system has to support several possible techniques. For instance, some portions of the website are served up as static files directly, so Apache authorization needs to be used (require valid-user or something), or some Apache module that gives a richer set of controls. But web applications often have shifting and eclectic permissions, so I don't want to use Apache to control those permissions (or at least not require that).

One reason I don't like 307 is because (a) programs that already expect REMOTE_USER usually respond with 401 (and I want to provide REMOTE_USER), and (b) the details of the redirect are best understood by Apache, not the individual applications. But if I have to use a redirect, I'll probably put the redirect location in an environmental variable, to at least keep the configuration centralized.

The POST thing is clearly a problem. It's one of many usability issues that POST proponents ignore. Obviously a POST-understanding redirector would be nice.

One of the advantages of a signed cookie is that identification happens once, and need not be efficient, and only the cookie verification happens on subsequent requests (which ought to be quite fast). But I'd like this all in C (which is what tkt offers) instead of mod_python, in part because mod_python is challenging to maintain in its own right, and not Apache 1.3 compatible.

# Ian Bicking