Getting Started with HAP

Welcome! This guide explains — in plain language — what this project is, gets it running on your machine, and walks you through seeing it actually work. No prior knowledge of logins, security, or "auth" jargon is assumed. If a word looks unfamiliar, check the Jargon, in plain words section.


What is this?

This project is the Headless Auth Platform — software that handles logging users in and proving who they are on behalf of other applications.

Think of it as the "front desk and security guard" for an app: it checks people in (sign up, log in), issues them a tamper-proof badge (a token) that proves who they are, supports extra security like authenticator-app codes and passkeys, and can plug into "log in with Google" or a company's single sign-on. It keeps an audit trail of what happened.

The twist is in the word headless: this platform has no screens of its own. It doesn't ship a login page. Instead, your application draws its own login screens and asks this platform questions behind the scenes (over a web API). That gives product teams full control of how login looks, while leaving the hard, security-critical parts (which are easy to get dangerously wrong) to a dedicated, standards-compliant engine.

Who is it for? Engineering teams that need solid authentication — including enterprise single sign-on — without building it themselves or locking into a paid vendor.


Jargon, in plain words

Even the "obvious" terms are spelled out here so nothing trips you up.

TermIn plain words
APIA way for one program to talk to another over the network (instead of a human clicking buttons). This platform is used through its API.
HeadlessSoftware with no built-in user interface/screens — you drive it through its API.
MonorepoOne Git repository that holds several related projects/parts together (rather than one repo each). This repo keeps planning docs, specs, and the app side by side.
TenantOne isolated customer/organisation inside the platform. Each tenant has its own users, settings, and keys; tenants can't see each other's data.
TokenA signed digital "badge" the platform issues after login. An app trusts it because it can verify the signature.
JWT (JSON Web Token)The common format for those tokens — a string that carries verifiable facts (who you are, when it expires).
OAuth 2.0The industry-standard rulebook for granting an app limited access on a user's behalf without sharing their password.
OIDC (OpenID Connect)A thin layer on top of OAuth 2.0 that adds "who is this user" (identity), via an ID token.
PKCE ("pixie")An extra OAuth safety step that stops a stolen authorization code from being used by an attacker. The platform requires it.
MFA (Multi-Factor Authentication)Requiring more than just a password — e.g., a code from an authenticator app or a one-time passcode.
TOTPThe 6-digit, 30-second codes an authenticator app (Google Authenticator, 1Password, etc.) generates.
Passkey / WebAuthn / FIDO2Passwordless login using your device's fingerprint/face or a security key. Phishing-resistant.
SSO (Single Sign-On)Logging in once with a central identity (e.g., your company account) to access many apps.
SAMLAn older but widely-used enterprise standard for SSO; common in large companies.
SCIMA standard for automatically creating/disabling user accounts from a company directory.
AAL (Authentication Assurance Level)"How sure are we it's really you?" AAL1 = password only; AAL2 = password + a second factor / a verified passkey.
Step-upAsking for extra proof (a second factor) only when something sensitive happens.

Before you start

You'll need three things installed:

  • Docker (with Docker Compose) — to run the database (PostgreSQL) and cache (Redis).
  • uv — a fast Python package manager (install guide).
  • A terminal and a copy of this repository.

The application itself lives in the target/ folder of this repo. Run the commands below from inside target/ unless noted otherwise.

cd target
uv sync --extra dev              # install Python dependencies into a local environment
cp .env.example .env             # create your local config

Open .env and set two values (everything else has working defaults):

# A 32-byte base64 key used to encrypt secrets at rest. Generate one:
#   python -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"
HAP_MASTER_ENCRYPTION_KEY=<paste the generated value>

# A password for the admin endpoints used in the demo below. Any strong string.
HAP_ADMIN_API_KEY=dev-admin-key

Then start the database and cache, and prepare the schema:

docker compose up -d postgres redis     # start PostgreSQL 16 + Redis 7 in the background
uv run alembic upgrade head              # create all database tables

See it run (≈60 seconds)

This quick check proves the platform is alive before you try a full login.

1. Start the service:

uv run uvicorn hap.main:app --reload

Leave it running and open a second terminal (also in target/) for the checks below.

2. Is it healthy?

curl -s localhost:8000/healthz      # → {"status":"ok"}
curl -s localhost:8000/readyz       # → {"status":"ok","checks":{"database":"ok","redis":"ok"}}

3. Can a client discover how to talk to it? Auth settings are per tenant, so first create one (the platform ships with none):

curl -s -X POST localhost:8000/v1/tenants \
  -H "X-Admin-Key: dev-admin-key" -H "Content-Type: application/json" \
  -d '{"name":"Demo","slug":"demo","password_policy":{"min_length":8}}'

Now fetch that tenant's public configuration (the "discovery document" every standards- compliant client reads to configure itself automatically):

curl -s localhost:8000/t/demo/.well-known/openid-configuration

Success looks like: a JSON document listing the tenant's login, token, and key-set URLs. If you see that, the platform is fully working. 🎉


Full demo: log in and get a token

This is the flagship flow — what the product is for. We'll create an app, register a user, log them in, and obtain a verifiable token, exactly as a real integrating application would. (Reading it through is informative even if you don't run every line.)

Assumes the service from the previous section is still running, and that you created the demo tenant above.

1. Register an application (the thing that will receive tokens). Note its client_id from the response:

curl -s -X POST localhost:8000/v1/applications \
  -H "X-Admin-Key: dev-admin-key" -H "X-Tenant-Id: <TENANT_ID_FROM_STEP_ABOVE>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Demo App","application_type":"spa",
       "redirect_uris":["https://example.com/callback"],
       "scopes":["openid","profile","email"]}'

The tenant's id was in the POST /v1/tenants response. redirect_uris must match exactly later, so we reuse https://example.com/callback.

2. Register and log in an end user (your app would render these screens):

# Register
curl -s -X POST localhost:8000/v1/auth/register \
  -H "X-Tenant-Id: <TENANT_ID>" -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"correct horse battery"}'

# Log in → returns a short-lived "login_token"
curl -s -X POST localhost:8000/v1/auth/login \
  -H "X-Tenant-Id: <TENANT_ID>" -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"correct horse battery"}'

3. Make a PKCE pair (the safety codes OAuth requires). This prints a verifier and a challenge:

python - <<'PY'
import os, base64, hashlib
v = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode()
c = base64.urlsafe_b64encode(hashlib.sha256(v.encode()).digest()).rstrip(b"=").decode()
print("verifier =", v)
print("challenge =", c)
PY

4. Authorize — exchange the login for a one-time code. Use the login_token, client_id, and challenge from above (-i shows the redirect with the code in the Location: header):

curl -si "localhost:8000/t/demo/oauth2/authorize?response_type=code\
&client_id=<CLIENT_ID>&redirect_uri=https://example.com/callback\
&scope=openid%20profile%20email&code_challenge=<CHALLENGE>\
&code_challenge_method=S256&login_token=<LOGIN_TOKEN>&consent=granted" | grep -i location
# → Location: https://example.com/callback?code=<CODE>&...

5. Exchange the code for tokens:

curl -s -X POST localhost:8000/t/demo/oauth2/token \
  -d grant_type=authorization_code -d code=<CODE> \
  -d redirect_uri=https://example.com/callback \
  -d code_verifier=<VERIFIER> -d client_id=<CLIENT_ID>

Success looks like a JSON response containing an access_token, an id_token (a JWT identifying the user), and a refresh_token.

6. Use the access token to fetch the user's profile:

curl -s localhost:8000/t/demo/oauth2/userinfo \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
# → {"sub":"...","email":"alice@example.com","email_verified":false,...}

That round trip — discovery → log in → authorize → token → user info — is the core of what this platform does for every application that uses it. From here you could add MFA, passkeys, "log in with Google", or enterprise SSO, all of which this platform supports.


Useful commands

Run these from inside target/:

CommandWhat it does
uv run uvicorn hap.main:app --reloadStart the platform locally (auto-reloads on changes).
docker compose up -d postgres redisStart the PostgreSQL + Redis dependencies.
docker compose downStop those dependencies.
uv run alembic upgrade headCreate/update the database tables.
uv run alembic downgrade baseTear the database schema back down.
uv run pytestRun the full test suite (spins up throwaway Postgres/Redis automatically).
uv run ruff check . && uv run ruff format --check .Lint and check formatting.
uv run mypy --strict src/hapRun strict type checking.
uv run python scripts/export_openapi.pyRegenerate the OpenAPI API description (openapi/openapi.json).
docker compose up --buildBuild and run the whole stack (app + Postgres + Redis) in containers.

Where to go next

  • target/README.md — the application's own README: quick start, configuration, and layout, with the full list of capabilities.
  • specs/001-headless-auth-platform/quickstart.md — a developer-focused quickstart for the platform (setup, run, test).
  • specs/001-headless-auth-platform/spec.mdwhat the platform does and why: user stories, requirements, and success criteria (non-technical friendly).
  • specs/001-headless-auth-platform/plan.md and data-model.mdhow it's built: architecture, technology choices, and the database design.
  • specs/001-headless-auth-platform/contracts/ — the API contracts (endpoints, request/response shapes) for the OAuth/OIDC and identity APIs.
  • target/openapi/openapi.json — the machine-readable API description (import it into Postman/Insomnia or generate client SDKs from it).
  • .specify/memory/constitution.md — the project's engineering principles (security- first, API-first, test-first, multi-tenant, and the /target layout rule).
  • initial-research/ — background research, the phased development plan, and the standards the platform implements.