Here's a pretty solid implementation I came up with
Through my search, I found tons of sloppy/incomplete implementations of BasicAuth for GAE online. As a result, I ended up writing my own. This is the best/simplest approach I have been able to come up with so far.
I consider it a 'good practice' to keep request handlers as thin as possible. To cut down on boilerplate and copypasta in the handlers themselves I decided to implement authentication as a decorator. To use it just attach the decorator to the handler's get/post/put/delete methods.
from decorators import basic_auth
# load your page here
Then add the decorator to decorators.py:
from webapp2_extras import security
def callf(webappRequest, *args, **kwargs):
# Parse the header to extract a user/password combo.
auth_header = webappRequest.request.headers.get('Authorization')
# if the auth header is missing popup a login dialog
if auth_header == None:
(username, password) = base64.b64decode(auth_header.split(' ')).split(':')
if(__basic_lookup(username) == __basic_hash(password)):
return func(webappRequest, *args, **kwargs)
webappRequest.response.set_status(401, message="Authorization Required")
webappRequest.response.headers['WWW-Authenticate'] = 'Basic realm="Secure Area"'
accounts_file = os.getcwd() + os.sep + 'app' + os.sep + 'accounts.yaml'
stream = file(accounts_file, 'r')
for account in yaml.load(stream):
if account['username'] == username:
return security.hash_password(password, method='sha1')
Note: Double underscore prefixed are used here on functions that shouldn't be visible outside of the 'decorators' module.
In this case, a failed login attempt will simply popup another login dialog, I'm authenticating using a password that is stored in a separate accounts.yaml file, and the passwords are stored in a SHA1 hashed form.
The code is written to be easily customized:
- Modify __basic_lookup() if you need your passwords are stored somewhere else (ex database).
- Modify __basic_hash() if your passwords are plaintext or encoded using a different method.
- Modify __basic_login() if you want a different response to a failed login attempt. Such as an artificial delay to prevent brute-force attacks, or a redirect.