| Title: | Simple Authentication |
|---|---|
| Author: | Ian Bicking <ianb@colorstudy.com> |
| Discussions-To: | Python Web-SIG <web-sig@python.org> |
| Status: | Proposed |
| Created: | 13-Nov-2006 |
Contents
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.
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.
There are two components to authentication:
- Indicating when a request is authenticated, and by who
- Responding that authentication is necessary
There are already two conventions for this:
- Put the username in REMOTE_USER
- Respond with 401 Unauthorized
Note
Please do not confused 401 Unauthorized with “permission denied”. Permission denied should be indicated with 403 Forbidden.
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
self.realm = realm
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
See Problems.