Introduction
As of AuthKit 0.4 it is possible to implement authentication middleware plugins as separate Python packages by making use of eggs and entry points.
 |
A good way to get started creating your own plugin is to base it on one of AuthKit's built in plugins since they also make use of the plugin architecture described in this page. Have a look at the source code of the authkit.authenticate module. |
Getting Started
In this example we will demonstrate how to create a plugin which redirects the user to a different URL if they are not signed in.
 |
This plugin is actually already part of AuthKit and you can use it as the redirect method, this tutorial is simply designed to show you how you might write such a plugin if you were doing so from scratch. |
First install AuthKit and check the authenticate_plugin template is available:
easy_install "AuthKit>=0.4"
paster create --list-templates
Available templates:
authenticate_plugin: An AuthKit authenticate middleware plugin
basic_package: A basic setuptools-enabled package
paste_deploy: A web application deployed through paste.deploy
If you see the authenticate_plugin option you can create a plugin, otherwise ensure you are using AuthKit 0.4 and not a previous version. You should take care to ensure you choose a name that isn't the name of an existing Python package such as AuthKit or Pylons, isn't the same as the names of any of the existing AuthKit authentication methods and isn't called setup or config. AuthKit will not warn you if you ignore this advice but you are likely to run into problems later on if you do.
We are going to call our plugin AlternativeRedirect. Create the template like this:
paster create --template=authenticate_plugin AlternativeRedirect
You will be asked some questions but you can just press enter to accept the defaults if you prefer.
If you look at the setup.py file which has been generated you will see that it looks a bit like this:
1
2
3
4
5
6
7
8
9
10 | setup(
...
install_requires = ['AuthKit>=0.4,<=0.5'],
...
entry_points = """
[authkit.method]
alternativeredirect = alternativeredirect:method_handler
""",
...
)
|
The install_requires line ensures that when this plugin is installed, AuthKit will be installed as well.
 |
If you are using the dev version of AuthKit just change the install_requires line to ["AuthKit"] and don't specify the version numbers or you might end up with pkg_resources.VersionConflict errors when you try to test your plugin. |
The entry_points line tells AuthKit that the method_handler() function in the alternativeredirect module can be called to setup this particular plugin if it is needed. All we need to do now is implement the plugin.
Plugin Requirements
This plugin is going to perform and external HTTP redirect to a specific URL so that the user can sign in at the new site. We want the user to be able to specify the URL to redirect to in the config file using this option:
authkit.alternativeredirect.url = http://example.com/signin
The URL specified needs to be passed to the Handler class so that when the handler is triggered it knows where to redirect to. There is one other issue we need to consider. The middleware we produce will be inserted into the middleware stack but we don't want it to redirect on every request, only when a sign in is needed. The AuthKit authenticate middleware has an option to specify which HTTP status codes a sign in should be triggered for so we should make sure that our handler also responds to the same HTTP codes but is not triggered for other status codes.
 |
The AuthKit authenticate middleware automatically converts exceptions such as the NotAuthenticatedError and NotAuthorisedError into responses with the correct HTTP codes so we simply need to worry about handling those HTTP responses, not the exceptions themselves. |
So in summary, we need to implement the following:
- Code to get the URL from the config file
- Code to pass the URL to the Handler
- Code in the handler to redirect the user
The Generated Code
The authentication plugin itself is defined in the AlternativeRedirect/alternativeredirect/_init_.py file which currently looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | from authkit.authenticate import middleware, AuthKitConfigError, strip_base
from authkit.authenticate.multi import MultiHandler, status_checker
class Handler:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# Implement your middleware here
pass
def make_handler(
app,
auth_conf,
app_conf=None,
global_conf=None,
prefix='authkit.method.alternativeredirect.',
):
app = MultiHandler(app)
app.add_method('alternativeredirect', Handler)
app.add_checker('alternativeredirect', status_checker)
return app
|
The make_handler() function is responsible for wrapping the application in middleware. It takes the following arguments:
| Argument |
Description |
| auth_conf |
The configuration options for this particular handler with the prefix removed |
| app_conf |
The original app_conf dictionary which was passed to the authentication middleware |
| global_conf |
The global_conf dictionary if one was passed to the authentication middleware |
| prefix |
The text which has been removed from the app_conf options to generate the auth_conf dictionary |
It is unlikely that you will need to worry about any of the options other than the specially prepared auth_conf dictionary which contains the configuration for your plugin.
Implementing the Code
As you can see the template has already setup a MultiHandler which triggers the Handler middleware to be inserted into the middleware stack whenever the status_checker finds the response is the same as one of the options setup in the authkit.setup.intercept option. Using the MultiHandler automatically takes care of the last requirement in our list.
Next we need to implement the Handler itself. We can do so like this.
1
2
3
4
5
6
7
8
9
10
11 | class Handler:
def __init__(self, app, redirect_to):
self.app = app
self.redirect_to = redirect_to
def __call__(self, environ, start_response):
start_response('302 Found', [
('Location',self.redirect_to),
('Content-Type','text/plain'),
])
return ['Redirecting to %s'%self.redirect_to]
|
The URL to redirect to can be obtained directly from the auth_conf dictionary:
if not auth_conf.has_key('url'):
raise AuthKitConfigError('No %surl config options was specified'%prefix)
redirect_to = auth_conf['url']
 | When choosing names for config options it is very important they they don't contain . or _ characters so that all the sub config options are at the same config option depth and so that they can all be specified directly in Python code. See Configuration for more information. |
 | Note the use of the prefix in generating the correct config option in the AuthKitConfigError raised. |
The Handler class will only be created if a sign in is triggered. We need to pass the URL as the redirect_to parameter to the Handler. We can pass any such options as arguments to the add_method() method of the MultiApp middleware like this:
1 | app.add_method('alternativeredirect', Handler, redirect_to)
|
Testing the Plugin
First of all install the plugin so that AuthKit can find it:
We can test the plugin with some sample code like this:
1
2
3
4
5
6
7
8
9
10 | from authkit.authenticate import middleware, sample_app
app = middleware(
sample_app,
setup_method='alternativeredirect',
alternativeredirect_url='http://3aims.com',
)
if __name__ == '__main__':
from paste.httpserver import serve
serve(app, host='0.0.0.0', port=8080)
|
If you visit http://localhost:8080
you should see the environment displayed but visiting http://localhost:8080/private
should trigger a sign in which will redirect you to http://3aims.com
.
Setting The REMOTE_USER
At this stage we have a plugin that correctly redirects to the sign in page but if they visit http://localhost:8080/private
again they will still be redirected because there is no middleware to set a REMOTE_USER. If for example the URL that has been redirected to sets a cookie when the user signs in we would need to modify our example to setup the cookie middleware so that the REMOTE_USER is set when the cookie is present and hence the sign in won't be triggered.
 |
If you choose to follow this approach it is essential that the cookie which is created once the user is signed in is setup with the same options which the middleware we are writing here will use to decode it, otherwise you will receive BadTicket errors when the middleware tries to load the cookie. |
The easiest way of setting this up is to simply specify in the config file that the method should be alternative, cookie to set up both middleware but it is also possible to setup the cookie middleware as part of the alternaitve method middleware too. Taking this second approach we can't simply modify our existing Handler class to add this functionality because it will only get called when a sign in occurs so wouldn't set the REMOTE_USER on pages where there were no authentication checks so instead we need to wrap the MultiHandler middleware in cookie middleware so that the cookie is always decoded on each request.
We'll set this middleware up next.
Using strip_base() to obtain options
In order to load the cookie middleware you will need to get the cookie options. Cookie options will be specified by your users in the config file and the options might look something like this::
authkit.alternativeredirect.cookie.secret = adadasd
... etc ...
In order to get just the options we want we can use the strip_base() function like this:
cookie_options = strip_base(auth_conf, 'cookie.')
This will filter out all the keys in auth_conf which don't start with cookie. and then strip cookie. from those which remain leaving you just with the cookie options.
We can set this up by importing the make_cookie_handler() function:
1 | from authkit.authenticate.auth_tkt import make_cookie_handler
|
then modifying make_handler() as follows so that the make_cookie_handler() function is added just before the app is returned:
1
2 | app = make_cookie_handler(app, auth_conf=cookie_options, prefix=prefix+'cookie.')
return app
|
Testing Our Finished Example
We can now test our finished example with the combined cookie middleware. Assuming the redirect is setup go to a URL which will set a cookie with the same options that can be read by the cookie middleware this example will work perfectly:
1
2
3
4
5
6
7
8
9
10
11 | from authkit.authenticate import middleware, sample_app
app = middleware(
sample_app,
setup_method='alternativeredirect',
alternativeredirect_url='http://3aims.com',
alternativeredirect_cookie_secret = "somesecret",
)
if __name__ == '__main__':
from paste.httpserver import serve
serve(app, host='0.0.0.0', port=8080)
|
 | If you are interested in seeing another plugin where there are two pieces of middleware involved have a look at the source code for the basic authentication method in authkit.authenticate.basic. This plugin uses an AuthBasicHandler combined with a MultiApp to handle signins whenever they are triggered and also TryToAddUsername middleware which tries to set REMOTE_USER on each request if a user is signed in |