#IndieAuth is a pretty neat thing, and as I'm already a big #RSS user (have been since they were in general use, just never dropped them) I guess it'd be cool to have what amounts to a way to log into various sites that I don't feel called to make single accounts for and certainly don't feel like giving access to my Gmail or Facebook.
Trouble is, I have very few places where I can put a ref="me" and a lot of the people I know don't either. Many sites don't let us edit the style sheet.
It's already been a year since IndieAuth was published as a W3C Note! A lot has happened in that time! There's been several new plugins and services launch support for IndieAuth, and it's even made appearances at several events around the world!
At API Days Global, oauth.io presented a session including IndieAuth.
Josh Hawxwell gave a talk at NottsJS called Indie What? where he covered several IndieWeb building blocks including IndieAuth.
In July, I wrote a blog post called OAuth for the Open Web, where I detailed the technical solutions IndieAuth provides on top of OAuth to enable it to work in a more open and less corporate environment.
In October, I published Dweb: Identity for the Decentralized Web with IndieAuth on the Mozilla Hacks Blog.
So here's to a productive year for IndieAuth in 2018! Looking forward to seeing what new developments come up in 2019!
OAuth has become the de facto standard for authorization and authentication on the web. Nearly every company with an API used by third party developers has implemented OAuth to enable people to build apps on top of it.
While OAuth is a great framework for this, the way it has ended up being used is much more centralized and closed than prior efforts like OpenID 1. Every service that spins up an OAuth-enabled API ends up being its own isolated system. For example, if I want to build an app that can read someone's step count from FitBit, I have to first go register as a developer on FitBit's website in order to get API keys to use with their OAuth API.
This works okay for major services like Google, Twitter, Facebook, and even FitBit, but breaks down when you start to consider use cases like having someone's personal WordPress blog be its own OAuth server. If I want to build an app that lets you upload photos to your WordPress site, I'm obviously not going to be able to register for API keys on everyone's own WordPress installations. Enabling third party clients to be built against systems like WordPress or Mastodon opens up a huge possibility for some really interesting things. The trick is always how do these apps authenticate the user or obtain a token they can use to access those APIs.
This post details a few specific challenges with OAuth preventing it from being used by independent websites, as well as the solutions to each.
The first major hurdle to overcome is the need for the developer to register to get API keys for the service. In a world where everyone's own website is its own OAuth server, it's obviously not practical to have an app developer register API keys at each.
In OAuth, client registration gives us a few specific things:
In order to avoid registration, we need a solution for the first three bullet points above.
Client ID: Every application needs a unique identifier. If we're talking about turning every website into an OAuth provider, we need a way to have globally unique identifiers for every OAuth app. It turns out we already have a mechanism for this: URLs! In this Open Web version of OAuth, client IDs can be the application's URL. For web-based apps, this is straightforward, as it's simply the website the app is running on. For native apps, this can be the application's "about" page.
Application name and icon: Since the application's client ID is a URL, we can assume every application has a web page that talks about it, and treat that web page as the place the client defines its own metadata like name and icon. A simple way to accomplish this is with Microformats, so that the application's web page just needs to add a couple classes around the name and icon of the app. This is currently documented and implemented as the h-app microformat.
Redirect URL registration: This one is a bit more subtle. The purpose of redirect URL registration is to prevent an attacker from tricking an authorization server into sending authorization codes to the attacker. This becomes especially important when we aren't using client secrets. The trick is that since client IDs are already URLs, we can shortcut the normal registration process by declaring a rule that redirect URLs have to be on the same domain as the client ID. This way, we can avoid a situation where an application claiming to be good.example.com sets a redirect URL to attacker.example.org and steals the authorization code. The only way to get the authorization code to attacker.example.org would be to set the client ID to that domain as well, which a user would hopefully notice.
There are two different situations to consider with regards to user accounts: authentication and authorization. Authentication is the process of proving the identity of the person signing in. Authorization is how an application obtains permission to do something to someone's account.
When we talk about authentication, we are talking about wanting to allow an unknown user to identify themselves to the site they're logging in to. Common examples of this are using your email address as your identity to sign in to a website. You bring an existing identity (your email address) and then authenticate (usually by clicking a link that was sent to your email). The original version of OpenID was created to solve this problem on the web. People identified themselves with a URL, which they were able to prove they controlled using OpenID. This allows a new user to log in to a site without needing a prior relationship with the site.
When we talk about authorization, the situation is subtly different. In this case, we're talking about a user of a website wanting to give permission to a third-party app to access some part of their account. We're very used to this pattern now, which is the typical OAuth use case of granting an application the ability to access your Google Calendar, or logging in to a third party Twitter app.
Authorization: There isn't really a challenge unique OAuth on the Open Web with regards to authorization. Once the client registration problem is solved, everything else falls into place nicely. It is assumed that users are authorizing an application to access an account they already have, so the application will just end up with an access token that works with their existing account.
Authentication: Where we need to define some new behavior is talking about authentication. In this case, we want users to be able to bring an existing identity and use it to log in to other places. This means we need a way to uniquely identify users across the entire web. We can again use URLs as the solution! Every user is identified by a URL. This can be a short URL like someone's domain name, e.g. https://aaronparecki.com/, or for a site with multiple users, can be a URL that contains a path specifying a particular user on the site, e.g. https://github.com/aaronpk.
With traditional OAuth services, discovery is not needed since the application author knows which OAuth server they're talking to before they start building the app. There is typically a "Sign in with ____" button in the application that begins the authorization process. In the case of using OAuth for authentication, the common pattern is to include buttons for several common "social login" providers such as Facebook, Google, Twitter and LinkedIn. Before the "social login" space essentially consolidated to these four, there were sometimes a dozen of these buttons on an application's login page, which eventually became known as the "NASCAR problem".
In a world where every WordPress or Gitlab site is its own OAuth provider, there obviously can't be a button for each on a login screen. Instead, we need to find out from the user which server to use to authenticate them.
Since we previously stated that every user identifier is a URL, we can ask the user to enter their URL in the sign-in screen, and then fetch that URL and discover their authorization server from there.
Once we've found the user's authorization endpoint, we can start a normal OAuth request and send them to their server to authenticate. When the server redirects back to the application, it will go and verify the authorization code with their authorization endpoint just like normally happens with OAuth.
While knowing any user identity information is technically not part of OAuth, we do need the server to return a user identifier when using OAuth for authentication. In practice, most applications also want at least a unique user identifier in the authorization case as well.
We've previously said that user identifiers are URLs, which solves the global user identity problem, and gives us a mechanism to discover the user's OAuth server. So all we need is a way to return this information to the application after the user has authenticated.
OAuth gives us an easy opportunity to return this to the application: in the access token response when the application sends the authorization code to obtain an access token. The server can at that point return the full user identifier of the user that logged in. As long as the domain name matches the domain that the user entered at the start, the application can consider it successful. This also gives the authorization server the opportunity to canonicalize the user identifier, correcting "http" to "https", or adding a path component to the user's profile URL.
By now, hopefully you're thinking "this sounds great, Aaron, someone should write this up as a OAuth extension!" I'm glad you asked!
Earlier this year, I wrote this all up as an extension to OAuth 2.0, called IndieAuth. IndieAuth encapsulates these small additions needed for OAuth 2.0 to work in the Open Web.
Despite this spec being published in January, it has actually been implemented for several years before that. There are many implementations of this extension on the server side, everything from standalone authorization server projects, to a WordPress plugin, and it's even implemented by a commercial service, Micro.blog. As far as consuming apps, nearly every Micropub app has implemented this for logging users in.
For further details on implementing this extension, there are several guides available depending on whether you're writing a client, a server, or just part of a server.
There are a few existing open source projects you can use to get started if you don't want to write your own!
An alternative possibility would be to prescribe the format of IndieAuth access codes, as part of the standard. For instance, we could prefix the usual arbitrary implementation-specific access code blob with the expected
me value, making it easy for token endpoints to discover the correct authorization endpoint.
code=https://00dani.me/$C5r1cuqJk1fUTGrWX4DPHz44jxpgHF or something like that. Then, of course, pure OAuth 2.0 clients would pass through that extra piece of information with no trouble whatsoever, since it's embedded in an existing standard parameter.
It's certainly a messy approach, though, and one might question whether OAuth client compatibility is worth adding this complexity to IndieAuth. Additionally, making a change like this now would introduce potential incompatibility: a token endpoint that knows it can pull information out of the access code might still receive an access code from an authorization endpoint that doesn't embed information in the prescribed format, for instance.
Still, prescribing a format for access codes might not be quite as unreasonable as it seems: after all, client IDs are also treated as opaque in pure OAuth 2.0, whereas in IndieAuth they have a prescribed and meaningful format.
tl;dr The more I think about it, the more I think this parameter enables a use case that isn't really necessary. The
me parameter in the code exchange step specifically allows for a token endpoint to be detached from both the Micropub endpoint and the authorization endpoint.
Full details below.
The different use cases that are all supported right now:
This is the simplest case in terms of architecture, but the most amount of work for a developer. In this case, someone writes all three parts of the system. Since they are part of the same system, the mechanism by which the token endpoint validates authorization codes does not need to be standardized, it's all internal.
Both my website and the Wordpress IndieAuth plugin fall under this case.
In this case, someone is building a CMS that includes a Micropub endpoint as well as a token endpoint. However, they want to speed up their development, so they use an authorization endpoint service such as indieauth.com.
The client sends the auth code to the token endpoint, and since the token endpoint is part of the CMS, it already knows the only place it can go to validate the auth code is the authorization endpoint service that it's configured to use. Therefore there is no need for the
me parameter, which normally tells the token endpoint where to go to verify the auth code.
Specifically this case is where a service provides both an authorization endpoint and token endpoint. This is the quickest path to building a Micropub endpoint, since all you need to do is build out the Micropub endpoint itself, and when any requests come in with a token, the endpoint goes and checks whether the token is valid by testing it against the token endpoint service.
This is a very common case with peoples' individual websites, as it offloads the development and maintenance of the security bits to a service. I provide these as a service at indieauth.com and tokens.indieauth.com.
The interesting thing though is that when a single service provides both, there is also no need for the
me parameter at the code exchange step, since the token endpoint already knows where it needs to verify the authorization code since the code was issued by the same system.
The only case where the
me is needed is when the authorization endpoint and token endpoint are both used as services and they are separate services. Imagine a standalone token endpoint service: the job of this service is to verify authorization codes and issue access tokens, and later verify access tokens. In this situation, a request comes in with an unknown authorization code and it needs to verify it. Since it was not part of the system that issued the code, it needs to know how to verify it. Right now, this is enabled because this request also includes the
me parameter, so the token endpoint goes and looks up the user's authorization endpoint and verifies the code there.
The thing I'm realizing though is that this is really quite an edge case, and one that I don't think is actually very important. Typically someone who is building a Micropub endpoint themselves will first start by using an authorization/token endpoint service, and there is no benefit to them if those are two separate services. In fact it's probably easier if they are just part of the same system since it's less moving parts to think about at this stage.
Later, that person can decide they want to take over issuing tokens, but still don't want to build out the UI of an authorization service. At this point, they fall under the second use case above. They build out a token endpoint into their software, and since they're using the authorization endpoint service they know where to verify authorization codes.
On the other end of the spectrum, you have people who build the whole thing out themselves, like my website and the Wordpress plugin. In these cases the
me is completely irrelevant in the code exchange step.
The particular situation that the
me enables is using a separate service for the authorization and token endpoints, and I can't think of a case where that is actually important.
What we really need is federated authentication, but that doesn't exist yet.
This sounds like a great use case for IndieAuth. w3.org/TR/indieauth
IndieAuth is an OAuth 2.0 extension, which avoids the centralized problems with existing OAuth solutions by using DNS for "registration" of client IDs and user IDs. Every user account is identified by a URL (for Gitea this could be your Gitea user page), and client IDs are also URLs (would be the Gitea instance home page in this case.)
To log in to your Gitea instance, I would enter my own Gitea profile URL. Your instance would then do discovery on my URL to find where to send me to authorize the login on my own OAuth server (my Gitea server), which would then send me back to your Gitea where it would be able to verify the authorization code against my Gitea instance.
I'd be happy to walk through this in more detail if you're interested!