Improved WSGI Basic authentication middleware in Python
While hunting around the Web for a Basic Authentication WSGI Middleware, I came across Edd Mann’s blog post that outlined a nice, elegant solution.
Supplying the same value in the username and password fields authenticates a user and hands over control to the wrapped WSGI application. A perfect solution to use in development environments, where in production the Web server configuration would handle authentication.
It however does not set the REMOTE_USER environment variable with the logged in username. Determining the logged in user would be quite a common use case for applications.
I made a few quick enhancements so the middleware passes on the user information to the WSGI application and lets you set the authentication realm name.
class BasicAuthMiddleware(object):
def __init__(self, app, realm="Login"):
self._app = app
self._realm = realm
self._usernmae = None
def __call__(self, environ, start_response):
if self._authenticated(environ.get('HTTP_AUTHORIZATION')):
environ['REMOTE_USER'] = self._username
return self._app(environ, start_response)
return self._login(environ, start_response)
def _authenticated(self, header):
from base64 import b64decode
if not header:
return False
_, encoded = header.split(None, 1)
decoded = b64decode(encoded).decode('UTF-8')
username, password = decoded.split(':', 1)
self._username = username
return username == password
def _login(self, environ, start_response):
start_response('401 Authentication Required',
[('Content-Type', 'text/html'),
('WWW-Authenticate', 'Basic realm="%s"' % self._realm)])
return [b'%s' % self._realm]
a basic WSGI demo application that displays the logged in username (adapted from Edd’s original example):
def app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b"Hello, %s!" % environ.get('REMOTE_USER')]
if __name__ == '__main__':
from wsgiref.simple_server import make_server
httpd = make_server('', 8080, BasicAuthMiddleware(app))
print('Serving on port 8080...')
try:
httpd.serve_forever()
except KeyboardInterrupt:
print('Goodbye!')