OpenID on App Engine made easy with AEoid

I'm pleased to present AEoid, a new App Engine library that aims to make user authentication with OpenID on App Engine simple.

AEoid is extremely easy to install, comprising a single piece of WSGI middleware, and its interface mirrors that of the App Engine Users API. Here's an example of it in action:

from aeoid import users

class TestData(db.Model):
  user = users.UserProperty()

class TestHandler(webapp.RequestHandler):
  def get(self):
    user = users.get_current_user()
    if not user:
      self.redirect(users.create_login_url(self.request.url))
      return
    logging.warn("Logged in as %s (%s)", user.nickname(), user.user_id())
    data = TestData(user=user)
    data.put()

As you can see, the interface to AEoid is almost exactly identical to the App Engine Users API. There a few differences of note:

  • Users are identified uniquely by their OpenID endpoint.
  • You can't construct a User object without specifying an OpenID URL.
  • Nicknames and email addresses are user-supplied, so they're not guaranteed unique or validated.
  • is_current_user_admin() is not yet implemented.
  • login: clauses in app.yaml are not affected by AEoid - they still authenticate using the regular Users API.

Installation

Installing AEoid is a simple matter of adding its WSGI middleware to your app. Here's an example for webapp:

application = webapp.WSGIApplication([
    # ...
], debug=True)
application = middleware.AeoidMiddleware(application)

For more installation details, see the readme.

AEoid takes the "convention over configuration" approach, so there are no mandatory configuration arguments - simply add the middleware and you're set. If you wish, though, you can customize it extensively: Currently, you can change the way sessions are handled, and in future you'll be able to customize the layout of the login page, the OpenID extensions you want to use, and other parameters.

How it works

The code behind AEoid is fairly straightforward, consisting mostly of glue code to make the python-openid and beaker sessions libraries work together nicely. Its workings are broken up into several components:

  • Request handlers that provide the login page, handle login requests, and log users out. These contain the bulk of the OpenID machinery. When a user submits their OpenID URL, a login handler creates an OpenID request and redirects them to their provider. Once login is complete, the provider redirects them back to AEoid's completion handler, which creates the user session and redirects them back to the app.
  • The public interface, in the form of a clone of the App Engine Users module. User objects consist of wrappers around a datastore entity that holds a user's information, and the various methods operate on this object. A UserProperty is also provided, which stores the key to the internal User object, while providing an interface identical to that provided by the datastore's native UserProperty.
  • Middleware code to make it all work. This takes care of wrapping your app in Beaker's session middleware, then, for each request, extracting the current user information for use by the Users API.

Right now, AEoid is in what I consider to be an 'early look' state. It's fully functional, but it's only lightly tested so far, and there's a lot of room for improvement - which will definitely be forthcoming. To see what's in the pipeline, check out the issue tracker.

If you decide to use AEoid, please leave feedback, as well as filing feature requests and bug reports in the issue tracker! In particular, if you use a framework other than webapp, please do try out AEoid with it, and let me know how it goes, so we can build up a compatibility list for the library. If you're really enthusiastic, don't hesitate to fork the library and contribute your own patches!

Comments

blog comments powered by Disqus