Authentication and authorization in Grok

Author:Jan-Wijbrand Kolman (j-w)
Version:This document is based on Grok-1.2.x

This document is an attempt to explain, into as much detail as needed, how the authentication and authorisation process is handled in Grok. This explanation is based on Grok-1.2 and the specific versions of its dependencies.

Note

This document most probably contains errors, ambiguities or ommisions. The reader is encouraged to discuss this document on the grok-dev mailing list and the #grok IRC channel.

Authentication versus authorisation

It is important to make a clear distinction between the terms “authentication” and “authorisation”. To a large extent, the Wikipedia definitions apply:

Authentication
Authentication (..) is the act of establishing or confirming something (or someone) as authentic, that is, that claims made by or about the subject are true(..) This might involve confirming the identity of a person(..).
Authorization
Authorization (also spelt Authorisation) is the function of specifying access rights to resources, which is related to information security and computer security in general and to access control in particular. More formally, “to authorize” is to define access policy(..)

Authentication

Grok - as it is built on top of the Zope Toolkit - has a particular name for the “actor” or “user” that is acting upon the application by issueing a request. This “actor” is called the principal.

The request object in Grok has a reference to an object that provides IPrincipal - the so called principal object. In these cases where authenticating the principal is not possible, the principal object will provide IUnauthenticatedPrincipal.

The principal is associated to the request object by several authentication attempts during the handling of a request. In other words, Grok tries to authenticate each and every request.

This is worth repeating: Grok will try to authenticate the principal for each and every request!

The authentication follows several steps:

Before traversal hook

At the very beginning of the request handling, just before object traversal is started, the publisher will call beforeTraversal() on the publication object (see zope.publisher.publish and zope.app.publication.zopepublication). This beforeTraversal() implementation will try to authenticate against the global IAuthentication utility.

By default, the global IAuthentication utility implementation that is registered in a Grok application, is that of the zope.principalregistry. It knows how to authenticate the current request using information from the application’s parts/etc/site.zcml.

When this “top-level” authentication is succesful, no other authentication attempts are made!

Traversal

The next step in the request handling is to traverse step by step to the requested object. For each step in the traversal, a callTraversalHook() is called on the publication object. In the callTraversalHook() implementation of this hook, an authentication attempt is undertaken - at least, as long as the object currently being “traversed over” is an ISite and actually has a IAuthentication utility registered for and the current principal on the request still provides IUnauthenticatedPrincipal!.

In other words, the very first authentication attempt that succeeds, wins!

After traversal

After the traversal has completed (mind you, the retrieved object is not called or “rendered” just yet!), the afterTraversal() hook is called on the publication object. The afterTraversal() implementation will do yet another authentication attempt.

If authentication has not succeeded thusfar, the request object will still have the unauthenticated principal associated. If authentication would have been succesful, the request will have and object associated providing IPrincipal. The implementation for this IPrincipal depends on the IAuthentication utility that did the authentication.

IAuthentication utilities

As we have seen, in each request there are several attempts to authenticate the request. This is done by calling authenticate() on the found utility. If the utility returns None Grok will continue with the IUnauthenticatedPrincipal, if it returns an IPrincipal object that object is used for the request.

In a Grok application, there’s at least a globally registered IAuthentication utility available, implemented in zope.principalregistry. Its authenticate() method will validate a loginname and password “pair” - also called the credentials - against a simple registry.

Actually retrieving the credentials however is delegated by this principal registry to the ILoginPassword adapter for the request. The ILoginPassword implementation for this adapter that is registered by default, will try to get Basic Authentication credentials from the request.

So, as long as the correct Basic Authentication credentials are available in the request, the principal will be authenticated.

Note

It is important to realize that other IAuthentication implementations might have a completely different strategy for authenticating requests. Later we will see how the often used PluggableAuthentication implements the authenticate() contract.

Authorization

User agents, like web browsers, do not by default provide Basic Authentication credentials in the requests they send to the application. They have to be triggered in doing so. Basic Authentication is triggered by a 401 status code in the response. But what mechanism will make sure a 401 status code is set on the response object in a Grok application?

Let’s assume we have view that is publicly accessible. Let’s also assume that we have a request for this view, that cannot be authenticated. The security mechanism will check whether the current request is allowed to access this particular view (exactly how this is checked is out of scope for this document. It would deserve an article on its own).

Since it is a public view, the security checking will allow it and the publisher will make sure the view is rendered.

Now suppose we have an unauthenticated request for another view, a view that is protected. Since we were not able to authenticate the request, we cannot determine whether the request should be allowed and the security mechanism will raise an Unauthorized exception.

Whenever an exception is raised in the publication process, the publisher will try to handle the situation in a clean manner by giving the publication the possibilty to do something about the error situation by calling the handleException() hook.

Part of the important house-keeping that is performed in the handleException() hook, is the lookup of a view for the current exception and having this “error” view rendered.

Grok has “error” views registered for several error situations, including a view that is specifically built to handle Unauthorized exceptions. This particular error view implementation will call unauthorized() on the request object which will set the 401 status code on the response, in turn triggering a login/password dialog of some sort in the user agent and that is the end of this request’s life-cycle.

This implies that the error view is responsible for somehow triggering the authentication challenge. If there is no such view, the result will just be a basic error message displayed, with not much more that the name of the current exception.

A new request could come in, now with credentials in the form of Basic Authentication, and the the new request cycle will follow the described process for authenticating the request. In the example of a protected view, we will now have an authenticated request and we can check whether the now-know principal actually was granted sufficient permissions to access this resource. (XXX refer to the documents about defining permissions and roles and how to restrict access on views).

Other implementations: the Pluggable Authentication Utility

Even though it is the default setup, actual real-world applications hardly ever use Basic Authentication for authenticating the request or the global principal registry for user management. Basic Authentication has fundamental security flaws (especially when using unencrypted connections) and so does the user experience.

One of the most often used alternative IAuthentication implementations is the flexible, but sometimes confusing, Pluggable Authentication Utility (PAU). This utility lives in the zope.pluggableauth package.

Note

This section will not have detailed explanation about installing the PAU and its plugins. This is left for a how to-type document.

Authenticating the request

As we have seen, Grok will attempt to authenticate each request by looking up an IAuthentication utility during the traversal. Suppose we have an Application object installed and an instance of the PAU registered for it.

The PAU then, will be used to authenticate the request, when traversed to this application. Its authenticate() implementation splits the authentication attempt in two steps (and plug-in points):

  1. Extract credentials for the request
  2. Validate the credentials

Extracting credentials

Credentials extraction is handled by a plugin point. Utilities that provide ICredentialsPlugin can be registered for this plugin point. The PAU is then configured to use these plugins, in a given order, in order to obtain credentials. The PAU will call extractCredentials() on each of the registered plugins and the first plugin that return a login-password pair “wins”.

For this section we will look at one specific, often used, credentials extraction implementation, called SessionCredentials.

Session Credentials plugin

The SessionCredentials plugin will try to extract credentials for the request by first looking for two specific keys in the form variables of the request, namely login and password. When values are present in the request for these keys, the values are stored in the session object.

Then, an attempt is made to get the credentials from the session object as they might have been stored just now, or have been stored already there in a previous request. If the user’s session object indeed contains credentials, these are handed back to the PAU. The PAU then continues with the second step, validating the login-password pair. If no credentials have been found, None is returned.

If None is returned, the PAU will try again in the next registered ICredentialsPlugin. If there are no more ICredentialsPlugin components registered for this PAU, the process ends, and the request will continue with an IUnauthenticatedPrincipal object for its associated principal.

Validating the extracted credentials

Now that the Pluggable Authentication Utility asked its credentials plugin for a login and password, the PAU needs to verify the validity of the login and password. Like for the ICredentialsPlugin components, the PAU will now ask each of the IAuthenticatorPlugin components that have been registered to verify the credentials. These components are supposed to either return an IPrincipal object, or None. In case no principal is returned, this cycle is repeated for the next ICredentialsPlugin component that has been registered until the list of registered components has been exhausted.

Valid credentials thus will result in an authenticated principal associated to the request. In words, the PAU now is “done”, and no other authentication attempts are being made for this request during the remaining part of the traversal, a view is looked up and - if the security mechanism allow for - rendered.

Not authorized, issueing an authentication challenge

Suppose we have an unauthenticated request for a protected resource. As the security machinery cannot determine whether this request is allowed to access the resource, an Unauthorized exception is raised and the the publication object will attempt to render an error view for this exception. We have seen that the default view for the Unauthorized exception will call unauthorized() on the request object. This would trigger the 401 status code on the response and the user agent would display some sort of dialog asking the user for Basic Authentication credentials.

That is however not necessarily the correct response, as components registered in the PAU probably require a different method of retrieving credentials! Instead of calling unauthorized() on the request, error views should call the unauthorized() method on nearest IAuthentication utility.

Note

Even though the default error view to handle Unauthorized exception will call the request.unauthorized() method, it is probably preferrable to call the getUtility(IAuthentication).unauthorized() from error views.

This means, a project that wishes to use the Pluggable Authentication components effecitively needs to create and register a custom view for the Unauthorized exception.

Attention

The Grok project might want to consider implementing a set of baseclasses for common error view, including one for ``Unauthorized`` and have it call the latter. This would unify the API for triggering the authentication challenge.

In fact the globally registered IAuthentication utility (that of the zope.principalregistry) implements the unauthorized() method as well, and will, by delegating to the ILoginPassword adaptation of the request, trigger the 401 response status code.

The PAU implements the unauthorized() method again in a pluggable manner. It will, like for the authenticate() part of the IAuthentication contract, delegate the actual challenge() to its ICredentialsPlugin components.

Challenge in the SessionCredentials plugin

The SessionCredentials plugin implements the challenge essentially as a redirect to a login page. The redirect URL will - if it can be computed - include a “camefrom” URL as a parameter. The login page should be a view containing an HTML form. It should have at least have two form fields, identified by the names login and password.

The form submit action should POST the form to the login page itself. Since Grok will try to authenticate each and every request, the POST request to the login form itself will also be authenticated. And as we have seen, the credentials extraction of the SessionCredentials plugin, will try to find values for login and password keys in the request. And this very request will have these values, as we justed “POST-ed” them!

The login page thus now has an authenticated principal available on the request and can decide to, in turn, redirect to the camefrom URL, closing the challenge-authenticate-view cycle.

How principals are created

...