eriksmartt.com>Selected Archives

How-To: Dynamic WWW-Authentication (.htaccess style) on Google App Engine

Sometimes classic Basic Access Authentication is the right approach to password protecting a webpage. It's not secure from sniffing, but functional if you're just trying to ward off the casual surfer in the wrong spot. (e.g., restricting access to your cat pictures, not your missile silo codes.)

Basic authentication is often added to sites (or directories) using an .htaccess file and something like this:

AuthUserFile /home/foo/.htpasswd
AuthName "Private Area"
AuthType Basic

<Limit GET>
require valid-user
</Limit>

...but you can also do basic authentication on-the-fly by reading/writing HTTP Headers. To ask the browser for a user/password, you can raise a 401 error and write a "www-Authenticate" header containing something like 'Basic realm="Secure Area"'. To read the user/password, look for an Authorization header, grab it's value, Base 64 decode it, and you should have a string in the form of "user:password".

Here's how you might handle it with Google App Engine. (Well, really you might use a decorator.. but this example is easier to explain.)

class AuthTest(webapp.RequestHandler):
  def get(self):

    # Wrapping in a huge try/except isn't the best approach. This is just 
    # an example for how you might do this.
    try:
      # Parse the header to extract a user/password combo.
      # We're expecting something like "Basic XZxgZRTpbjpvcGVuIHYlc4FkZQ=="
      auth_header = self.request.headers['Authorization']

      # Isolate the encoded user/passwd and decode it
      auth_parts = auth_header.split(' ')
      user_pass_parts = base64.b64decode(auth_parts[1]).split(':')
      user_arg = user_pass_parts[0]
      pass_arg = user_pass_parts[1]

      checkAuth(user_arg, pass_arg) # have this call raise an exception if it fails

      self.response.out.write(template.render('templates/foo.html', {}))

    except Exception, e:
      logging.debug("AuthTest Exception: %s" % (e))

      # Here's how you set the headers requesting the browser to prompt
      # for a user/password:
      self.response.set_status(401, message="Authorization Required")
      self.response.headers['WWW-Authenticate'] = 'Basic realm="Secure Area"'

      # Rendering a 401 Error page is a good way to go...
      self.response.out.write(template.render('templates/error/401.html', {}))

That's all there is to it.

If you want to programatically write an Authorization header (as in, sending authentication credentials to another site, like the Twitter APIs, for example) you'll do something like this:

request = urllib2.Request(url)
request.add_header('Authorization', "Basic %s" % (base64.b64encode("%s:%s" % (user, password))))

Enjoy!