No description
Find a file
2026-08-24 19:05:18 -07:00
.gitignore Initial Authentik-backed IndieAuth bridge 2026-08-24 09:28:35 -07:00
cache.go Harden IndieAuth protocol and identity binding 2026-08-24 18:08:45 -07:00
go.mod Initial Authentik-backed IndieAuth bridge 2026-08-24 09:28:35 -07:00
go.sum Initial Authentik-backed IndieAuth bridge 2026-08-24 09:28:35 -07:00
indieauth-bridge.service Initial Authentik-backed IndieAuth bridge 2026-08-24 09:28:35 -07:00
main.go Retry transient Authentik JWKS failures 2026-08-24 19:05:18 -07:00
main_test.go Retry transient Authentik JWKS failures 2026-08-24 19:05:18 -07:00
README.md Retry transient Authentik JWKS failures 2026-08-24 19:05:18 -07:00
store.go Recheck ownership throughout authorization 2026-08-24 18:14:08 -07:00
urls.go Recheck ownership throughout authorization 2026-08-24 18:14:08 -07:00

IndieAuth Authentik Bridge

A small IndieAuth authorization server for 32bit.cafe. It uses Authentik for browser authentication, maps member identity URLs to immutable Authentik user UUIDs, and issues opaque IndieAuth authorization codes, access tokens, and rotating refresh tokens.

The OAuth authorization-server metadata document is served at:

https://indieauth.32bit.cafe/.well-known/oauth-authorization-server

Identity mapping

Every two minutes the daemon reads active users from the Authentik API and atomically replaces an on-disk bbolt key/value cache.

  • Each value in a user's administratively managed indieAuthSites attribute maps to that user's immutable UUID. Do not expose this attribute for unrestricted self-service editing; it is an ownership assertion.
  • username.32bit.cafe is included unless indieAuthMain is explicitly false.
  • A bare domain is canonicalized to https://domain/.
  • Profile URLs require HTTP(S), a domain name, no port, fragment, credentials, or dot path segments. An empty path is normalized to /.
  • Conflicting claims fail the entire refresh and retain the last-known-good cache.
  • Service accounts and inactive users are excluded.

OAuth and IndieAuth behavior

  • Authorization Code grant with mandatory PKCE S256
  • Scope-less authentication codes redeem only at POST /authorize and return me; they never produce access or refresh tokens
  • Authentik OIDC Authorization Code flow, also protected by PKCE and nonce
  • Bounded exponential retry when Authentik's JWKS endpoint has a transient fetch failure; deterministic signature and claim failures are never retried
  • Exact Authentik OIDC sub-to-user-UUID ownership check
  • Optional me, with identity selection after login when necessary
  • Explicit scope allowlist; email requires profile, and refreshes may narrow an access token without narrowing the underlying refresh grant
  • Explicit downstream client consent after Authentik login
  • Same-origin IndieAuth redirect URIs, or cross-origin URIs declared by the public client_id JSON metadata, HTTP Link, or HTML <link> metadata
  • Same-origin-only metadata redirects and special-use-address SSRF protections
  • Opaque, hashed-at-rest authorization codes and tokens
  • One-hour access tokens and rotating 30-day refresh tokens
  • HTTP Basic-authenticated token introspection, revocation, and documented legacy IndieAuth token verification via GET /token
  • A last-known-good cache only while it is at most ten minutes old; startup and authorization fail closed without a fresh cache. Ownership is rechecked at login callback and atomically again when consent becomes a code.
  • HTTPS-only hosted web clients. HTTP, loopback, and custom-scheme clients are intentionally outside this deployment's compatibility policy.

Build and test

Go 1.20 or newer is required.

go test ./...
go build -trimpath -ldflags='-s -w' -o indieauth-bridge .

For a Linux amd64 deployment from another platform:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' -o indieauth-bridge .

Configuration

The service reads configuration from environment variables:

Variable Purpose
PUBLIC_BASE_URL Public issuer URL, normally https://indieauth.32bit.cafe
AUTHENTIK_ISSUER Provider issuer, normally https://auth.32bit.cafe/application/o/indieauth/
AUTHENTIK_CLIENT_ID Confidential Authentik OAuth client ID
AUTHENTIK_CLIENT_SECRET Confidential Authentik OAuth client secret
AUTHENTIK_API_BASE Authentik API base, normally https://auth.32bit.cafe/api/v3
AUTHENTIK_API_TOKEN Token for a service account with only authentik_core.view_user
INTROSPECTION_CLIENT_ID Resource-server credential used with HTTP Basic authentication
INTROSPECTION_CLIENT_SECRET Separate high-entropy introspection secret
TRUST_PROXY_HEADERS Set to true only behind the local trusted reverse proxy, for per-client rate limits
LISTEN_ADDR Local listen address; defaults to 127.0.0.1:9084
DATA_PATH bbolt state path; defaults to /var/lib/indieauth/indieauth.db

Keep the environment file root-readable only. The included systemd unit expects it at /etc/indieauth/indieauth.env and runs the daemon as a dedicated indieauth system user.

Authentik provider

Create a confidential OAuth2/OpenID provider and application with:

  • Application slug: indieauth
  • Strict redirect URI: https://indieauth.32bit.cafe/oidc/callback
  • Issuer mode: per provider; subject mode: user UUID
  • ID-token claims enabled
  • Scope mappings: openid, profile, and email
  • Authorization flow: the normal Authentik implicit-consent provider flow

The bridge presents its own consent screen for the downstream IndieAuth client; Authentik's job is only to authenticate the member seamlessly.

HTTP endpoints

Endpoint Purpose
/.well-known/oauth-authorization-server RFC 8414 metadata
/authorize GET starts authorization; POST redeems scope-less login codes
/oidc/callback Authentik callback
/token Code and refresh grants; bearer-token verification on GET
/introspect HTTP Basic-authenticated token introspection
/revoke Token revocation
/healthz Cache freshness and service health

Apache or another TLS reverse proxy should forward this host to http://127.0.0.1:9084/ and preserve the public hostname.