This post will show you step by step how you can let people log in to your website with their own IndieAuth website so you don't need to worry about user accounts or passwords.
What is IndieAuth? IndieAuth is an extension of OAuth 2.0 that enables an individual website like someone's WordPress, Gitea or OwnCast instance to become its own identity provider. This means you can use your own website to sign in to other websites that support IndieAuth.
You can learn more about the differences between IndieAuth and OAuth by reading OAuth for the Open Web.
What You'll Need
You'll need a few tools and libraries to sign users in with IndieAuth.
- An HTTP client.
- A URL parsing library.
- A hashing library that supports SHA256.
- A library to find
<link>
tags in HTML. - The ability to show an HTML form to the user.
IndieAuth Flow Summary
Here is a summary of the steps to let people sign in to your website with IndieAuth. We'll dive deeper into each step later in this post.
- Present a sign-in form asking the user to enter their server address.
- Fetch the URL to discover their IndieAuth server.
- Redirect them to their IndieAuth server with the details of your sign-in request in the query string.
- Wait for the user to be redirected back to your website with an authorization code in the query string.
- Exchange the authorization code for the user's profile information by making an HTTP request to their IndieAuth server.
Step by Step
Let's dive into the details of each step of the flow. While this is meant to be an approachable guide to IndieAuth, eventually you'll want to make sure to read the spec to make sure you're handling all the edge cases you might encounter properly.
Show the Sign-In Form
First you'll need to ask the user to enter their server address. You should show a form with a single HTML field, <input type="url">
. You need to know at least the server name of the user's website.
To improve the user experience, you should add some JavaScript to automatically add the https://
scheme if the user doesn't type it in.
The form should submit to a route on your website that will start the flow. Here's a complete example of an IndieAuth sign-in form.
<form action="/indieauth/start" method="post">
<input type="url" name="url" placeholder="example.com">
<br>
<input type="submit" value="Sign In">
</form>
When the user submits this form, you'll start with the URL they enter and you're ready to begin the IndieAuth flow.
Discover the IndieAuth Server
There are potentially two URLs you'll need to find at the URL the user entered in order to complete the flow: the authorization endpoint and token endpoint.
The authorization endpoint is where you'll redirect the user to so they can sign in and approve the request. Eventually they'll be redirected back to your app with an authorization code in the query string. You can take that authorization code and exchange it for their profile information. If your app wanted to read or write additional data from their website, such as when creating posts using Micropub, it could exchange that code at the second endpoint (the token endpoint) to get an access token.
To find these endpoints, you'll fetch the URL the user entered (after validating and normalizing it first) and look for <link>
tags on the web page. Specifically, you'll be looking for <link rel="authorization_endpoint" href="...">
and <link rel="token_endpoint" href="...">
to find the endpoints you need for the flow. You'll want to use an HTML parser or a link rel parser library to find these URLs.
Start the Flow by Redirecting the User
Now you're ready to send the user to their IndieAuth server to have them log in and approve your request.
You'll need to take the authorization endpoint you discovered in the previous request and add a bunch of parameters to the query string, then redirect the user to that URL. Here is the list of parameters to add to the query string:
response_type=code
- This tells the server you are doing an IndieAuth authorization code flow.client_id=
- Set this value to the home page of your website the user is signing in to.redirect_uri=
- This is the URL where you want the user to be returned to after they log in and approve the request. It should have the same domain name as theclient_id
value.state=
- Before starting this step, you should generate a random value for thestate
parameter and store it in a session and include it in the request. This is for CSRF protection for your app.code_challenge=
- This is the base64-urlencoded SHA256 hash of a random string you will generate. We'll cover this in more detail below.code_challenge_method=S256
- This tells the server which hashing method you used, which will be SHA256 or S256 for short.me=
- (optional) You can provide the URL the user entered in your sign-in form as a parameter here which can be a hint to some IndieAuth servers that support multiple users per server.scope=profile
- (optional) If you want to request the user's profile information such as their name, photo, or email, include the scope parameter in the request. The value of the scope parameter can be eitherprofile
orprofile email
. (Make sure to URL-encode the value when including it in a URL, so it will end up asprofile+email
orprofile%20email
.)
Calculating the Code Challenge
The Code Challenge is a hash of a secret (called the Code Verifier) that you generate before redirecting the user. This lets the server know that the thing that will later make the request for the user's profile information is the same thing that started the flow. You can see the full details of how to create this parameter in the spec, but the summary is:
- Create a random string (called the Code Verifier) between 43-128 characters long
- Calculate the SHA256 hash of the string
- Base64-URL encode the hash to create the Code Challenge
The part that people most often make a mistake with is the Base64-URL encoding. Make sure you are encoding the raw hash value, not a hex representation of the hash like some hashing libraries will return.
Once you're ready with all these values, add them all to the query string of the authorization endpoint you previously discovered. For example if the user's authorization endpoint is https://indieauth.rocks/authorize
because their website is https://indieauth.rocks
, then you'd add these parameters to the query string to create a URL like:
https://indieauth.rocks/authorize?response_type=code
&client_id=https://example-app.com
&redirect_uri=https://example-app.com/redirect
&state=a46a0b27e67c0cb53
&code_challenge=eBKnGb9SEoqsi0RGBv00dsvFDzJNQOyomi6LE87RVSc
&code_challenge_method=S256
&me=https://indieauth.rocks
&scope=profile
Note: The user's authorization endpoint might not be on the same domain as the URL they entered. That's okay! That just means they have delegated their IndieAuth handling to an external service.
Now you can redirect the user to this URL so that they can approve this request at their own IndieAuth server.
Handle the Redirect Back
You won't see the user again until after they've logged in to their website and approved the request. Eventually the IndieAuth server will redirect the user back to the redirect_uri
you provided in the authorization request. The authorization server will add two query parameters to the redirect: code
and state
. For example:
https://example-app.com/redirect?code=af79b83817b317afc9aa
&state=a46a0b27e67c0cb53
First you need to double check that the state
value in the redirect matches the state
value that you included in the initial request. This is a CSRF protection mechanism. Assuming they match, you're ready to exchange the authorization code for the user's profile information.
Exchange the Authorization Code for the User's Profile Info
Now you'll need to make a POST request to exchange the authorization code for the user's profile information. Since this code was returned in a redirect, the IndieAuth server needs an extra confirmation that it was sent back to the right thing, which is what the Code Verifier and Code Challenge are for. You'll make a POST request to the authorization endpoint with the following parameters:
grant_type=authorization_code
code=
- The authorization code as received in the redirect.client_id=
- The sameclient_id
as was used in the original request.redirect_uri=
The sameredirect_uri
as was used in the original request.code_verifier=
The original random string you generated when calculating the Code Challenge.
This is described in additional detail in the spec.
Assuming everything checks out, the IndieAuth server will respond with the full URL of the user, as well as their stated profile information if requested. The response will look like the below:
{
"me": "https://indieauth.rocks/",
"profile": {
"name": "IndieAuth Rocks",
"url": https://indieauth.rocks/"
"photo": "https://indieauth.rocks/profile.jpg"
}
}
Wait! We're not done yet! Just because you get information in this response doesn't necessarily mean you can trust it yet! There are two important points here:
- The information under the
profile
object must ALWAYS be treated as user-supplied data, not treated as canonical or authoritative in any way. This means for example not de-duping users based on theprofile.url
field orprofile.email
field. - If the
me
URL is not an exact match of the URL the user initially entered, you need to re-discover the authorization endpoint of theme
URL returned in this response and make sure it matches exactly the authorization server you found in the initial discovery step.
You can perform the same discovery step as in the beginning, but this time using the me
URL returned in the authorization code response. If that authorization endpoint matches the same authorization endpoint that you used when you started the flow, everything is fine and you can treat this response as valid.
This last validation step is critical, since without it, anyone could set up an authorization endpoint claiming to be anyone else's server. More details are available in the spec.
Now you're done!
The me
URL is the value you should use as the canonical and stable identifier for this user. You can use the information in the profile
object to augment this user account with information like the user's name or profile information. If the user logs in again later, look up the user from their me
URL and update their name/photo/email with the most recent values in the profile
object to keep their profile up to date.
Testing Your IndieAuth Client
To test your IndieAuth client, you'll need to find a handful of IndieAuth providers in the wild you can use to sign in to it. Here are some to get you started:
- Micro.blog - All micro.blog accounts are IndieAuth identities as well. You can use a free account for testing.
- WordPress - With the IndieAuth plugin installed, a WordPress site can be its own IndieAuth server as well.
- Drupal - The IndieWeb module for Drupal will let a Drupal instance be its own IndieAuth server.
- Selfauth - Selfauth is a single PHP file that acts as an IndieAuth server.
Eventually I will get around to finishing the test suite at indieauth.rocks so that you have a testing tool readily available, but in the mean time the options above should be enough to get you started.
Getting Help
If you get stuck or need help, feel free to drop by the IndieWeb chat to ask questions! Myself and many others are there all the time and happy to help troubleshoot new IndieAuth implementations!