[Web-SIG] New spec: simple authentication

Ian Bicking ianb at colorstudy.com
Tue Nov 14 04:54:49 CET 2006


I added a new spec describing authentication middleware with WSGI.  It 
doesn't describe anything new, really, it just describes what I think is 
the most basic best practice in doing WSGI-based authentication middleware:

   http://wsgi.org/wsgi/Specifications/simple_authentication

I offer it more as a basis for other specifications to build upon.

Also copied below:

:Title: Simple Authentication
:Author: Ian Bicking <ianb at colorstudy.com>
:Discussions-To: Python Web-SIG <web-sig at python.org>
:Status: Proposed
:Created: 13-Nov-2006

.. contents::

Abstract
--------

This describes a simple pattern for implementing authentication in WSGI 
middleware.  This does not propose any new features or environment keys; 
it only describes a baseline recommended practice.

Rationale
---------

Authentication is probably the most common detail that should be 
abstracted away from an application, as it is a concern most often bound 
to a *deployment*.

Specification
-------------

There are two components to authentication:

  1. Indicating when a request is authenticated, and by who
  2. Responding that authentication is necessary

There are already two conventions for this:

  1. Put the username in ``REMOTE_USER``
  2. Respond with ``401 Unauthorized``

.. note::
    Please do not confused ``401 Unauthorized`` with "permission 
denied".  Permission denied should be indicated with ``403 Forbidden``.

``REMOTE_USER``:
     This should be the string username of the user, nothing more.
``401 Unauthorized``:
     Because middleware is handling the authentication, additional 
information is not required.  You do not (and should not) include a 
``WWW-Authenticate`` header.  The middleware may include that header, or 
may change the response in some other way to handle the login.

Example
--------

The first example implements simple HTTP Basic authentication::

   class HTTPBasic(object):

       def __init__(self, app, user_database, realm='Website'):
           self.app = app
           self.user_database = user_database

       def __call__(self, environ, start_response):
           def repl_start_response(status, headers, exc_info=None):
               if status.startswith('401'):
                   remove_header(headers, 'WWW-Authenticate')
                   headers.append(('WWW-Authenticate', 'Basic 
realm="%s"' % self.realm))
               return start_response(status, headers)
           auth = environ.get('HTTP_AUTHORIZATION')
           if auth:
               scheme, data = auth.split(None, 1)
               assert scheme.lower() == 'basic'
               username, password = data.decode('base64').split(':', 1)
               if self.user_database.get(username) != password:
                   return self.bad_auth(environ, start_response)
               environ['REMOTE_USER'] = username
               del environ['HTTP_AUTHORIZATION']
           return self.app(environ, repl_start_response)

       def bad_auth(self, environ, start_response):
           body = 'Please authenticate'
           headers = [
               ('content-type', 'text/plain'),
               ('content-length', str(len(body))),
               ('WWW-Authenticate', 'Basic realm="%s"' % self.realm)]
           start_response('401 Unauthorized', headers)
           return [body]

   def remove_header(headers, name):
       for header in headers:
           if header[0].lower() == name.lower():
               headers.remove(header)
               break

Problems
--------

* Strictly speaking, it is illegal to send a ``401 Unauthorized`` 
response without the WWW-Authenticate header.  If no middleware is 
installed, most browsers will treat it like a ``200 OK``.  There is also 
no way to detect if an appropriate middleware is installed.

* This doesn't give any other information about the user.  That 
information can go in other keys, but that is not addressed in this 
specification currently.

* Some login methods will redirect the user, and any POST request data 
will possibly be lost.  (Note that a specification like 
["handling_post_forms"] helps address this problem.)

Other Possibilities
-------------------

* While you can add to this specification, I think it's the most logical 
and useful way to do authentication and better efforts can build on this 
base.

Open Issues
-----------

See Problems.


More information about the Web-SIG mailing list