Documentation

For app developers

IndieKey is a standard IndieAuth server. Any client that follows the current specification works with it; this page summarizes what that means.

If you only need to know who someone is, the simplest route is indielogin.com, which handles all of the steps below for you.

1. Discover the server

Fetch the URL the user entered (following redirects) and look for rel="indieauth-metadata" in an HTTP Link header or an HTML <link> element. Fetch that URL to get the server metadata:

{
  "issuer": "https://indiekey.id/s/SITE_ID/",
  "authorization_endpoint": "https://indiekey.id/s/SITE_ID/auth",
  "token_endpoint": "https://indiekey.id/token",
  "introspection_endpoint": "https://indiekey.id/introspect",
  "revocation_endpoint": "https://indiekey.id/revoke",
  "userinfo_endpoint": "https://indiekey.id/userinfo",
  "code_challenge_methods_supported": ["S256"],
  "authorization_response_iss_parameter_supported": true,
  ...
}

Each website using this server is its own authorization server, with its own metadata URL, issuer and authorization_endpoint. The token, introspection, revocation and userinfo endpoints are shared. Always use the endpoints from the metadata you discovered for the user's website.

2. Publish client metadata

Your client_id is a URL. Serve a JSON document there so the consent screen can show your app's name and logo:

{
  "client_id": "https://app.example.com/",
  "client_name": "Example App",
  "client_uri": "https://app.example.com/",
  "logo_uri": "https://app.example.com/logo.png",
  "redirect_uris": ["https://app.example.com/callback"]
}

A redirect_uri on a different scheme, host or port from the client_id is only accepted if it is listed in redirect_uris. Apps without a metadata document still work, but the user only sees the bare URL. The older h-app microformat is not read.

3. Send the user to the authorization endpoint

https://indiekey.id/s/SITE_ID/auth?response_type=code
  &client_id=https://app.example.com/
  &redirect_uri=https://app.example.com/callback
  &state=RANDOM_STATE
  &code_challenge=BASE64URL(SHA256(VERIFIER))
  &code_challenge_method=S256
  &scope=profile create
  &me=https://user.example.net/
  • state and PKCE with S256 are required.
  • scope is optional. Leave it out if you only need to authenticate the user.
  • me is optional. The authorization endpoint already belongs to one website, so that is who the user signs in as.

The user returns to your redirect_uri with code, state and iss. Check that state matches and iss equals the issuer from the metadata.

4. Exchange the code

To get an access token, POST to the token endpoint within 60 seconds:

curl https://indiekey.id/token \
  -d grant_type=authorization_code \
  -d code=CODE \
  -d client_id=https://app.example.com/ \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier=VERIFIER
{
  "access_token": "…",
  "token_type": "Bearer",
  "scope": "profile create",
  "expires_in": 3600,
  "refresh_token": "…",
  "me": "https://user.example.net/",
  "profile": { "name": "…", "url": "https://user.example.net/", "photo": "…" }
}

If no scope was granted, no access token is issued and only me is returned. If you only need to know who signed in, POST the same parameters to the authorization endpoint instead, which always returns just me (and profile if granted).

A scope the user's Micropub or Microsub endpoint accepts gives you a JWT that endpoint can verify itself (see verifying access tokens), lasting an hour by default, with a refresh_token to renew it. A sign-in token, with only profile or email, is an opaque string that lasts a week and has no refresh_token: ask the user to sign in again.

5. Verify the returned me

If the returned me differs from what the user entered, fetch it and confirm its metadata has the same issuer as the iss you received before trusting it. Because every website has its own issuer, this check also catches a sign-in for a website whose home page now links somewhere else.

Refreshing and revoking

curl https://indiekey.id/token \
  -d grant_type=refresh_token \
  -d refresh_token=REFRESH_TOKEN \
  -d client_id=https://app.example.com/

Refresh tokens rotate: each use returns a new one, and presenting an old one again revokes the grant. They expire after 90 days without use.

To sign out, POST token=… to https://indiekey.id/revoke. This revokes the access token and every token issued with it.

Userinfo

With a token that has the profile or email scope, GET https://indiekey.id/userinfo with an Authorization: Bearer header to fetch the current profile.