23 Jan 2017
The asyncio-portier library
15 Feb 2017
Mozilla Persona was an authentication solution that sought to preserve user privacy while making life easy for developers (as explained here). You may have noticed the past tense there — Persona has gone away. A new project, Portier, picks up where Persona left off, and I’m eager to see it succeed. I’ll do anything to avoid storing password hashes or dealing with OAuth. With that in mind, here is my small contribution: a guide to using Portier with Python’s asyncio.
The Big Idea
So far, the official guide to using Portier is… short. It tells you to inspect and reuse code from the demo implementation. So that’s what we’ll do! But first, let’s take a look at how this is supposed to work.
The guide says
From the developer’s perspective, that means
- A user submits their e-mail address via a
POSTto an endpoint on your server.
- Your server
- Generates and stores a nonce for this request.
- Replies with a redirect to an authorization endpoint (as of today, that
means hardcoding Portier’s Broker URL:
- Your server will eventually receive a
POSTto one of its endpoints (
/verifyin the demo implementation). At this point
- If the data supplied are all valid (including checking against the nonce created in step 2.1.), then the user has been authenticated and your server can set a session cookie.
- If something is invalid, show the user an error.
This is all well and good… provided that you’re using Python and your server code is synchronous. I generally use Tornado with asyncio for my Python web framework needs, so some tweaks need to be made to get everything working together nicely.
If you want to use something other than Python, I can’t really help you. I did say Portier is new, didn’t I?
For some background, Python 3.4 added a way to write non-blocking single-threaded code, and Python 3.5 added some language keywords to make this feature easier to use. For the sake of brevity I’ll include code that works in Python 3.5 or later. Here is a blog post describing the changes in case you need to use an earlier version of Python.
For those of you using the same setup that I do (Tornado and asyncio), refer to this page for getting things up and running.
This code does not need to be modified to work with asyncio. I’ll include what it should look like when using Tornado, though. Assuming that
REDISis a Redis connection object as from redis-py
SETTINGSis a dictionary containing your application’s settings
SETTINGS['WebsiteURL']is the URL of your application (such as
SETTINGS['BrokerURL']is the URL of the Portier Broker,
This does need some modification to work. Assuming that you have defined an
exception class for your application called
This function only needs two straightforward changes from the demo implementation:
This function needs three changes from the demo implementation. The first is simple again:
The second change is in the line with
res = urlopen(''.join((broker,
'/.well-known/openid-configuration'))). The problem is that
blocking, so you can’t just
await it. If you’re not using Tornado, I
recommend using the aiohttp library (refer to the
client example). If you are using Tornado, you can use the
The third change is similar to the second:
urlopen again. Solve it the same
Down the line, there will be client-side Portier libraries (or, at least, other demo implementations) for various languages. Until then, you’ll need to do some of the heavy lifting yourself. I think it’s worth it, and I hope you will, too.
Comments: View this post's comment section here.