[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