This is still the header! Main site

IndieAuth: an intuitive explanation

2020/06/21

IndieAuth is a really nice application of OAuth2, using your own website to authenticate. There exist already quite some descriptions of how it works, how and why it's different from OAuth (... along with the actual W3 note)... so you can read those if you want to implement some of it. However... I think there might be a lack of information on why things work the way they do, making all the requests & responses fairly convoluted and pointlessly bureaucratic.

The goal of this article is quite the opposite: we'll try to give you an intuitive feel on why we need to pack all these things in the requests, what's the difference between different tokens, and how all this fits together. You'll hear about:

The scenario

As promised, for additional intuitiveness, here is our cast of characters, borrowed from computer security literature:

Authentication

(... out of authEntication vs. authOrization , this is the "who are you" part. Since "E", which is the first different character in the two, comes first in the alphabet, just like you first need to know who you're talking to before you can decide what they're allowed to do.)

If we start right from the math-y extremes, we can have the following exchange:

Alice (... over HTTPS): hi! I'm Alice. Please post a message, titled "Pangolins are the Best", having the text "... just because.".

alice-site.example.net: Sure!!! *puts new post on site*

a pangolin. By Piekfrosch, CC-BY-SA 3.0.

Well... obviously... there is not much security in this. Fun fact: this is pretty much how Lisp Machines worked: you login by giving it a username, no other questions asked. But... while this works well within a smallish group of people... it really doesn't work on The Internet.

Mallory (... over HTTPS): hi! I'm Alice. Please post a message, titled "Pangolins Suck", having the text "... go cockroaches.".

alice-site.example.net: Sure!!! *puts new post on site*

So... we invented passwords.

Mallory (... over HTTPS): hi! I'm Alice. Please post a message, titled "Pangolins Suck", having the text "... go cockroaches.". PS: my password is "PangolinHater23".

alice-site.example.net: Um... how about "no"? 403 sorry.

But then... where do passwords come from? Well, Alice can either set one up by hand on the server:

        alice@alice-site:~$ sudo echo "password=PangolinsAreAwesome777" \
            >/var/www/alice-site/config/my_password.conf
      

but... by this time the server knows already who Alice is (... she did log in via ssh, after all). Alternatively, we could do something like web registration:

Alice: hi! Can I grab the name "alice" on your site? My password could be, say, "GoPangolins3".

bobs-example-blog-network.net: sure! It hasn't been claimed yet; welcome!

Alice: hi! Can I add a post? I'm "alice", password "GoPangolins3". It's... about... pangolins. [... post content here.]

bobs-example-blog-network.net: sure! *posts content*

However... in this case, we don't quite care who we're interacting with... as long as it's consistent:

Mallory: hi! Can I grab the name "alice" on your site? My password could be, say, "12345".

bobs-example-blog-network.net um... that's taken; want to pick something else?

Mallory: How about "evil_toady_overlord"? Password is the same.

bobs-example-blog-network.net sure! Feel free to post.

Mallory: hi! I'm "evil_toady_overlord". Here is a picture of toads. Password: "12345".

bobs-example-blog-network.net sure!

a toad. by Marek Szczepanek, CC-BY-SA 3.0 posted by: evil_toady_overlord

(... in which case... Mallory didn't quite end up being as malicious as he usually is; posting things under his own name is entirely legit.)

So... to summarize: unless you're a bank who cares about whether you're a US citizen or not, you don't have to care much about the actual identity of the user. It's all about just giving them a key and then ensuring that what they've posted / created on the site (... including e.g. the reputation of their username, connections, etc), is only accessible to them (... defined as: people who hold the same key / password).

This works nicely if you only want to do things within a site. However... it breaks down if it has to work between them. How do you prove to a site that you're the same Alice as the one on that other site? (Which we'll need if we want these sites to talk to each other.)

The "logging in using your site" part

So... passwords work in a way like this: I'm Alice. Don't let anyone post in my name unless they're me; they can prove this by telling you the password, "GoPangolins3", which only I know.

IndieAuth, on the other hand, is along the lines of... I'm Alice. I have the site alice-site.example.net. Don't let anyone in unless they can prove that they own that site. This has the benefit of it possibly working even without the user and the site having shared a secret before, as long as they can agree on what they mean by "proving that they own the site".

There is actually many ways of doing that. Let's Encrypt, for example, lets you pick between serving a HTML page at the address or adding a TXT DNS record. They both look somehow like this:

Site Admin: I want this cert signed for my-awesome-site.example.com!

Let's Encrypt Server: sure; here is the code "hach3AhgeeTh6bur(...)"; if you can put this on the site, we'll believe it's yours & will sign the cert.

Site Admin: *does server config things* done!

Let's Encrypt Server: *makes HTTP / DNS request* ... OK it's indeed on the site! *signs and returns cert*

With IndieAuth... well, here is the short version:

Alice, to bobs-example-blog-network.net, via HTTPS: hey, it's Alice of alice-site.example.net! Can I post more pangolins?

bobs-example-blog-network.net: ... let's see. So you're claiming you own alice-site.example.net, right?

Alice Yep!

bobs-example-blog-network.net: OK then. Well, let's look at this site. alice-site.example.net... it exists, excellent; let me see what's on it. It says your authorization endpoint is "https://alice-site.example.net/auth"; could you please get an auth code out of it and tell that to me at "https://bobs-example-blog-network.net/redirect"?

...

Alice, to alice-site.example.net/auth, via HTTPS: hi! can you give me an auth code to bobs-example-blog-network.net? Thanks!

alice-site.example.net/auth: ... um, who are you exactly and why should I give you one?

Alice it's Alice, password "GoPangolins3"!

alice-site.example.net/authsounds good, hi, welcome home! Well, the auth code is "aev6faeK(...)"; tell this to Bob.

...

Alice, to bobs-example-blog-network.net/redirect: hey! it's Alice of alice-site.example.net again... I just talked to my site, and I now have the auth code. It's "aev6faeK(...)"!

bobs-example-blog-network.net/redirect: lol. You just made that up right now. Everyone can come up with some stupid-looking random characters.

Alice meh. Sure, if you really want to, go ask my site, they'll vouch for me.

...

bobs-example-blog-network.net to alice-site.example.net: hey alice-site. So... there is this person claiming you gave her an auth code. It's "aev6faeK(...)". Is this true?

alice-site.example.net: yeah, sure, just gave it to her, she's Alice, she likes pangolins, do you like pangolins?

bobs-example-blog-network.net *hangs up*

...

bobs-example-blog-network.net, to Alice: ... OK sure, looks like you're Alice indeed. You have... 20 posts already. 5 private messages. What would you like to post about today?

At least by now we can see why each of the steps is necessary. We have bobs-example-blog-network.net talking to Alice, for obvious reasons: she wants to post something. For the time being, it's Alice's site (with the password auth) that has the power to tell Alice apart from everyone else, so they too also need to talk. And, finally, Alice needs something to show to bobs-example-blog-network.net that is obviously coming from her site. In a world where everything needs to be HTTP requests and responses, this is kinda tricky: Alice could ask her site to ping Bob's, but, sadly, client-side certs aren't things that are really being used, so servers have no idea who the current request is from:

Mallory (to bobs-example-blog-network.net) hey, I got the auth code from my site alice-site.example.net; it's 12345!

bobs-example-blog-network.net well, everyone can make up codes...

[unidentified requestor, from... some IP address] I'm alice-site.example.net, I just gave M... Alice the code, it's totally legit!

This is the issue being fixed by it being Bob's site asking Alice's for confirmation: this way Bob's can be sure who it's talking to.

Giving permissions to sites to act in your name

This is the fancy part of OAuth2.0: the one where the app X gets to do things on site Y in the name of user Z. Don't worry, we'll clean this up.

Let's say... Alice wants to give permission to David's Endangered Species Newsletter Factory to periodically post pangolin-related posts in her name to Bob's blog. Actually, the flow is fairly similar to the "just login" one:

Alice, to davids-newsletters-example.com: hi! would you please start posting to Bob's forum?

davids-newsletters-example.com: sure! Well, please get me an authorization code first and ping me back at davids-newsletters-example.com/redirect. Looking at your site... you can go to alice-site.example.net/auth.

...

Alice (to her site): hi it's me! *adds password*.

alice-site.example.net: niceness! what can I do for you?

Alice: can we please authorize the client davids-newsletters-example.com to post on Bob's? Just "post" as a scope; nothing else.

alice-site.example.net: gladly. Here it is: *hands over the auth code "weTh0zie(...)" to Alice's browser, which promptly redirects to David's.

Alice (to davids-newsletters-example.com/redirect): here we go: "weTh0zie(...)". Should be valid for "post"-ing, and it also says "alice-site.example.net".

davids-newsletters-example.com, to bobs-example-blog-network.net/token: hello. Here it says *waves auth code around* that I can post here as alice-site.example.net. I guess this expires in 10 minutes though. Can you give me a token so that I can continue posting here?

bobs-example-blog-network.net/token to alice-site.example.net: do you mind verifying this?

alice-site.example.net: ohh yes this is totally true. Just gave this out a couple minutes ago.

bobs-example-blog-network.net/token (back to David's): OK then. You shall take this Bearer Token then; It will grant Powers to Post as Alice for Two Fortnights, Or Until Revoked! We trust you; handle with care. "Thuchu4a(...)"

... a week later ...

davids-newsletters-example.com: here I come with a new post. I am the Bearer of "Thuchu4a(...)"! I act in the Name of Alice!

bobs-example-blog-network.net is this true? *checks database* OK, we indeed gave this out having seen an auth code from Alice, valid for "post". Let me guess: pangolins.

In essence, for both cases, it's the authorization endpoint that is declaring to various entities that you or your delegate (David's site, in this case) is indeed doing what you want; it gets to do this because it has the special position of sitting on your domain, which fact everyone can verify. In the above examples, it is proving to Bob's site that Alice's browser is indeed hers; it's also showing Bob's site that David's site has your permission to post. It's up to the two sides to keep talking to each other though! In the case of Bob's forum and the browser, the site will probably send out a cookie with a session ID to make Alice stay logged in; it's just an implementation detail within Bob's site, which is okay, since it's Bob's code both in the browser and on the backend. Between Bob's and David's, it's David's code talking to Bob's backend, so we need some kind of standardized way of establishing a session... and that's what token endpoints are for.

Endpoints and trust

Based on all of the above, it might appear that authorization endpoints generally belong to the users who want to prove their identity, while token endpoints are kept by sites where users want to log into. This is not necessarily the case though.

To begin with, users can outsource this task, for example, by specifying an authorization endpoint that's not actually run by themselves, but who they trust with making authorization decisions for them, after, possibly, asking them about this. Alternatively, this can also happen implicitly: RelMeAuth works by having a server look at your site and verifying your identity via silo accounts.

However, it's also important to understand that while you can get pwned by your authorization endpoint authorizing the wrong person, the ultimate decision is eventually in the hands of whoever has the data (e.g. Bob's forum, in the above examples). Of course, Alice has to trust her own endpoint (so that it doesn't give up auth codes to Mallory), but she also has to trust Bob's site so that it actually asks her endpoint (properly) about all this. This might seem trivial, but it could still be worth thinking about.

Ready-to-go services

Given how easy it is to outsource some of these endpoints, you can get away without actually implementing a lot on your site.

Conclusions

You now know (roughly) how IndieAuth works. We didn't quite cover topics like why you need to check redirect URIs and how CSRF attacks are being defended against (... maybe there will be a followup article?); we did go through the general ideas though... so you can read through the spec and follow along.

Hope this article was useful to understand what's going on. If you have any ideas how to make it better / fix something that's just plain wrong (... I'm new to the topic, too, so this could totally happen), feel free to drop me an email at simon@simonsafar.com. Also, webmentions technically work, but they won't quite show up yet :)