indiko documentation

IndieAuth/OAuth 2.0 server with passkey authentication

overview

Indiko is a self-hosted IndieAuth/OAuth 2.0 authorization server with passwordless authentication using WebAuthn passkeys. It provides single sign-on (SSO) for your apps and services.

key features

getting started

for app developers

To integrate with Indiko as an OAuth client, you'll need:

  1. A client ID (any valid URL, e.g., https://myapp.example.com)
  2. A redirect URI (where users return after authorization)
  3. Support for PKCE (code challenge/verifier)
Auto-registration: Apps are automatically registered on first use. You don't need admin approval to get started. During registration, Indiko fetches your client metadata from your client_id URL to validate redirect URIs and display your app name/logo. For advanced features like client secrets and role assignment, contact your Indiko admin to pre-register your app.

publishing client metadata (recommended)

To help Indiko verify your app and display proper branding, publish client metadata as JSON at your client_id URL:

{
  "client_id": "https://myapp.example.com/",
  "client_name": "My App",
  "logo_uri": "https://myapp.example.com/logo.png",
  "redirect_uris": [
    "https://myapp.example.com/callback",
    "https://myapp.example.com/auth/callback"
  ]
}

Alternatively, you can publish redirect URIs as HTML <link> tags:

<link rel="redirect_uri" href="https://myapp.example.com/callback" />
Security: If your redirect_uri uses a different host than your client_id, you MUST publish redirect_uris in your client metadata. This prevents unauthorized apps from hijacking your client_id.

for users

You'll need an invite code to create an account. Once registered:

sign in button

Copy this themed button for your app's login page. It matches Indiko's visual style:

HTML + CSS

Customization: Replace YOUR_OAUTH_URL_HERE with your authorization URL (see authorization flow below). You can also change the button text or adjust colors to match your app's theme.

API endpoints

authorization endpoints

Endpoint Method Description
/.well-known/oauth-authorization-server GET IndieAuth server metadata (discovery endpoint)
/auth/authorize GET Start OAuth authorization flow
/auth/authorize POST Submit consent/scope approval
/auth/token POST Exchange code for access token and refresh token
/auth/token/introspect POST Verify access token validity
/auth/token/revoke POST Revoke access or refresh token
/userinfo GET Get user profile data with bearer token
/u/:username GET Public user profile (h-card with discovery links)

authentication endpoints

Endpoint Method Description
/auth/can-register POST Check if invite code is valid
/auth/register/options POST Get WebAuthn registration options
/auth/register/verify POST Complete passkey registration
/auth/login/options POST Get WebAuthn login options
/auth/login/verify POST Complete passkey login
/auth/logout POST End current session

authorization flow

0. discovery (recommended)

Before starting authorization, clients should discover the authorization server's endpoints from the user's profile URL:

  1. Fetch the user's profile URL (e.g., http://localhost:3000/u/username)
  2. Look for <link rel="indieauth-metadata"> tag or HTTP Link: header
  3. Fetch the metadata endpoint to get authorization_endpoint and token_endpoint

The metadata endpoint returns:

{
  "issuer": "http://localhost:3000",
  "authorization_endpoint": "http://localhost:3000/auth/authorize",
  "token_endpoint": "http://localhost:3000/auth/token",
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["profile", "email"]
}

1. redirect to authorization endpoint

GET http://localhost:3000/auth/authorize?response_type=code
					&client_id=https://myapp.example.com
					&redirect_uri=https://myapp.example.com/callback
					&state=random_state_string
					&code_challenge=base64url_encoded_challenge
					&code_challenge_method=S256
					&scope=profile email
PKCE is required: Generate a random code_verifier (43-128 characters), then create code_challenge by hashing it with SHA-256 and base64url encoding.

2. user authenticates and approves

Indiko will:

3. redirect back with code

https://myapp.example.com/callback?code=short_lived_authorization_code
					&state=random_state_string
					&iss=http://localhost:3000
Security: The iss (issuer) parameter allows you to verify the response came from the expected authorization server. Compare it to the issuer from the metadata endpoint.

4. exchange code for token

POST http://localhost:3000/auth/token
					Content-Type: application/x-www-form-urlencoded

					grant_type=authorization_code
					&code=authorization_code
					&client_id=https://myapp.example.com
					&redirect_uri=https://myapp.example.com/callback
					&code_verifier=original_code_verifier
					&client_secret=your_client_secret (if pre-registered)
Client authentication: All clients MUST use PKCE (code_verifier) per the IndieAuth specification. Pre-registered confidential clients should also include client_secret in the token request for additional security.

5. receive tokens and user profile

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "RT_abc123xyz...",
  "me": "http://localhost:3000/u/username",
  "profile": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "photo": "https://example.com/photo.jpg",
    "url": "https://jane.example.com"
  },
  "scope": "profile email",
  "iss": "http://localhost:3000",
  "role": "admin"
}
Token types:
  • access_token - Short-lived token (1 hour) for API access
  • refresh_token - Long-lived token (30 days) for getting new access tokens
Roles: If an admin has assigned a role to this user for your app, it will be included in the response. Roles are arbitrary strings that you can use for role-based access control (RBAC) in your application.

token management

Indiko provides a complete OAuth 2.0 token management system with access tokens, refresh tokens, introspection, and revocation.

refresh tokens

Access tokens expire after 1 hour. Use the refresh token to get a new access token without requiring user re-authentication:

POST http://localhost:3000/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=RT_abc123xyz...
&client_id=https://myapp.example.com

Response:

{
  "access_token": "new_access_token...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "me": "http://localhost:3000/u/username",
  "scope": "profile email",
  "iss": "http://localhost:3000"
}
Important:
  • Refresh tokens are valid for 30 days
  • Each refresh request generates a new access token
  • The refresh token itself remains valid (no rotation)
  • Store refresh tokens securely - they provide long-term access

token introspection

Resource servers can verify access tokens by calling the introspection endpoint:

POST http://localhost:3000/auth/token/introspect
Content-Type: application/json

{
  "token": "access_token_here"
}

Response (valid token):

{
  "active": true,
  "me": "http://localhost:3000/u/username",
  "client_id": "https://myapp.example.com",
  "scope": "profile email",
  "exp": 1640000000,
  "iat": 1639996400
}

Response (invalid token):

{
  "active": false
}
Use case: Introspection is useful for resource servers (like Micropub endpoints) that need to verify tokens issued by Indiko.

token revocation

Apps can revoke access or refresh tokens when users log out:

POST http://localhost:3000/auth/token/revoke
Content-Type: application/json

{
  "token": "access_or_refresh_token_here"
}

Response: HTTP 200 (always returns success, even if token doesn't exist)

Best practice: Always revoke tokens when users explicitly log out to prevent unauthorized access.

userinfo endpoint

Fetch updated user profile information using an access token:

GET http://localhost:3000/userinfo
Authorization: Bearer access_token_here

Response (with profile and email scopes):

{
  "name": "Jane Doe",
  "photo": "https://example.com/photo.jpg",
  "url": "https://jane.example.com",
  "email": "jane@example.com"
}
Note: The response only includes data for scopes granted to the token. A token with only profile scope will not include email.

scopes

Scope Description Data Included
profile Basic profile information name, photo, URL
email Email address email
Note: Users can selectively approve scopes during authorization. Your app may receive fewer scopes than requested.

roles

Roles enable role-based access control (RBAC) in your applications. Only pre-registered clients with client secrets support role assignment.

Pre-registration required: To use roles, contact your Indiko admin to pre-register your app with a client secret. Auto-registered (public) clients cannot use roles.

how roles work

Example use case: A CMS app could use roles like "admin", "editor", and "viewer". When users authenticate via Indiko, the app checks their role and grants appropriate permissions.

defining app roles

Apps can define available roles in three ways:

assigning roles

Roles can be assigned in multiple ways:

  1. Default role (automatic): If configured, users automatically receive the default role on first authorization.
  2. Via invite codes: Admins can create invites with pre-assigned roles for specific apps. New users automatically get those roles on signup.
  3. Via admin dashboard: Admins can assign or change roles for existing users in the clients management interface.
Note: Roles are optional. If no role is assigned, the role field will not appear in the token response.

client types

auto-registered clients

Any app can use Indiko without pre-registration. On first authorization, Indiko will:

Auto-registered clients:

Security: For redirect URIs on different hosts than your client_id, you must publish redirect_uris in your client metadata. See getting started for details.

pre-registered clients

Admins can pre-register clients for advanced features. All pre-registered clients require a client secret and must also use PKCE.

Pre-registered clients:

Tip: Contact your Indiko admin to pre-register your app if you need client authentication or role-based access control.

OAuth tester

Test the OAuth flow with a live interactive client. This simulates how your app would integrate with Indiko.

How it works: This page uses the current URL as the redirect URI. After authorization, the code is automatically detected and you can exchange it for user profile data.