# Guard In Depth

Woven Guard is in a state of flux at the moment. We apologize for the absence of a stable, supported API for web-based authentication, which we recognize is a very broadly needed feature, but it is surprisingly difficult to get it correct in a multi-protocol context. (Remember, Twisted is a lot more than just a web server!) Please help the woven development team correct this situation by keeping up with the latest improvements to the API and giving requirements and use-cases for the deployment of guard-based sites. Stay tuned!

Woven guard is founded on the twisted.cred framework, and it is important to be somewhat familiar with cred to work with guard. As always, when using this framework, start by writing a `IRealm`. The guard module expects the realm to respond to the `resource.IResource` interace.

Here is an example of a simple realm which generates different resources for logged-in and non-logged-in users:

    from twisted.cred import portal, checkers
    from twisted.web import resource, static

    class Realm:
        __implements__ = portal.IRealm
        def requestAvatar(self, avatarId, mind, *interfaces):
            if resource.IResource not in interfaces:
                raise NotImplementedError
            if avatarId is checkers.ANONYMOUS:
                return static.Data("Who are you?", 'text/html')
            return static.Data("The answer is 42", 'text/html')

This realm makes sure that only non-anonymous users will know the answer to life, the universe and everything.

Usually, one or more custom resource classes will be written when using guard, but it is not necessary. For example, a guard intended to protect a certain directory can use a `static.File` resource. Note that the `IRealm` should be prepared to get the `ANONYMOUS` avatar ID, and handle it correctly. From whatever resource the anonymous avatar gets, a link should point to `guard.INIT_PERSPECTIVE` relative to the guard root. This is the only way users can log in. From whatever resources the non-anonymous avatars get it is strongly recommended to link to `guard.DESTROY_PERSPECTIVE`. This is the only way users can log out.

    from twisted.cred import portal, checkers
    from twisted.web import resource, static
    from twisted.web.woven import guard

    login='<a href="%s">login</a>' % guard.PERSPECTIVE_INIT
    logout='<a href="%s">logout</a>' % guard.PERSPECTIVE_DESTROY

    class Realm:
        __implements__ = portal.IRealm
        def requestAvatar(self, avatarId, mind, *interfaces):
            if resource.IResource not in interfaces:
                raise NotImplementedError
            if avatarId is checkers.ANONYMOUS:
                return static.Data("Who are you?<br>"+login, 'text/html')
            return static.Data("The answer is 42, %s<br>%s" % (avatarId, logout),
                               'text/html')

Once the realm is written, it is possible to generate a resource which will wrap it with appropriate code to manage users, sessions and authentication. But, as always, nothing deals with the realm directly -- all the rest of the code deals with a `Portal` which wraps the realm.

You will almost always want to put `checkers.AllowAnonymousAccess` in the checkers registered for the `Portal`, otherwise it will be impossible to log in.

The canonical thing is to use `resource=guard.SessionWrapper(guard.UsernamePasswordWrapper(portal, callback=callback)`. The `callback` is used to redirect the request to the appropriate place after a successful login. Usually, you will want to redirect to the parent:

    def parentRedirect(_):
        return util.ParentRedirect()

When a client first reaches a guarded resource, it is redirected to `session-init/`. From there, it will be redirected to a URL containing a long magic hex string, where a cookie will be set, and then to the original URL with `?__session_just_started__=1` tucked at the end. The addition is to guarantee that the client will not think it is in a redirection loop (wget, for example, has that problem).

Note that in resources which are children of the guarded resources, `request.getSession` automatically returns the Woven session. Since it is a `Componentized`, it is possible to use `getComponent` and `setComponent` to keep state related to a user in the session.

For simple cases, the approach described here leads to quite a bit of boiler-plate code (about 30 lines or so). If a web application has simple authentication needs, it is possible to use `simpleguard`, which allows you to skip implementing a realm yourself.

The important function in `simpleguard` is `resource=guardResource(resource, checkers, nonauthenticated=None)`. `checkers` should be a list of `ICredentialChecker`s. It is not necessary to put `AllowAnonymousAccess` here -- it will be added automatically. This allow you to differentiate in resources only based on authenticated/anonymous users, without finer distinction. However, in the given resources, and their children, it is possible to use `request.getComponent(simpleguard.Authenticated)`. This will return `None` if the request is anonymous, or an instance with a `.name` attribute, which is the avatar ID. This can allow the application to personalize parts of it.

The way the login page and error-on-login page look can be customized when creating the guarded resource. Here is an example:

listings/guard/login.py

The trick here is that, of course, we define the original login page: while `guard.PERSPECTIVE_INIT` gives a default form, it is quite all right to use a different one. In this example, we just use the home page of non-authenticated users to show the login form. There is one other case where the default form will be shown: if the attempt to login did not succeed. However, this is only what happens by default: we can override this by supplying an `errback`. This will be called with a model describing the form, and all we have to do is to use woven to display it.

Note that this example uses `simpleguard`, but equivalent code can be developed using `guard` -- the errback should be passed to the `UsernamePasswordWrapper` constructor in that case.