Free-IdP Hosting Guide
Free-IdP Hosting Guide
This guide walks through standing up a persistent, self-hosted OpenApe IdP using the reference openape-free-idp app — a Nuxt 4 project that wires @openape/nuxt-auth-idp to a libsql database (either a local SQLite file or Turso).
If you only need a dev sandbox or want to bring your own persistence layer, see Quick Start for the minimal path.
What you get
- Passkey login (WebAuthn) and SSH-key agent auth out of the box.
- OAuth/OIDC endpoints (
/authorize,/token,/.well-known/openid-configuration,/userinfo, JWKS). - Grant approval flows at
/grants,/grant-approval,/enroll. - Admin UI at
/adminfor users, agents, sessions, and registration URLs. - Optional multi-tenant hosting — one instance, many domains.
- A working schema with migrations, backfills, and indexes you can iterate on.
Prerequisites
- Node 22+
- pnpm 10+
- A libsql target. Either:
- Local file (recommended for development and single-node self-hosting) — any path on disk
- Turso (recommended for HA or multi-region) — a free-tier Turso DB works
Setup
1. Clone the reference app
git clone https://github.com/openape-ai/openape
cd openape
pnpm install
cd apps/openape-free-idp
2. Configure the database
The database is accessed through @libsql/client → drizzle-orm. useDb() reads tursoUrl and tursoAuthToken from runtimeConfig:
// apps/openape-free-idp/server/database/drizzle.ts
import { createClient } from '@libsql/client'
import { drizzle } from 'drizzle-orm/libsql'
export function useDb() {
const config = useRuntimeConfig()
const client = createClient({
url: config.tursoUrl as string, // file:./data/idp.db OR libsql://...
authToken: config.tursoAuthToken as string, // empty for file:// URLs
})
return drizzle(client, { schema })
}
Option A — local SQLite file
export NUXT_TURSO_URL="file:./data/idp.db"
export NUXT_TURSO_AUTH_TOKEN=""
The DB file is created on first startup. The startup plugin server/plugins/02.database.ts runs idempotent CREATE TABLE IF NOT EXISTS statements for every table the module needs.
Option B — Turso
turso db create my-idp
turso db tokens create my-idp
export NUXT_TURSO_URL="libsql://my-idp-YOURORG.turso.io"
export NUXT_TURSO_AUTH_TOKEN="eyJhbGciOi…"
3. Configure the IdP module
apps/openape-free-idp/nuxt.config.ts already includes the module. Set the runtime secrets:
export NUXT_OPENAPE_IDP_SESSION_SECRET=$(openssl rand -hex 32)
export NUXT_OPENAPE_IDP_MANAGEMENT_TOKEN=$(openssl rand -hex 32)
export NUXT_OPENAPE_IDP_ADMIN_EMAILS="you@example.com"
For a single-hostname deployment, also set:
export NUXT_OPENAPE_IDP_RP_ID=id.example.com
export NUXT_OPENAPE_IDP_RP_ORIGIN=https://id.example.com
export NUXT_OPENAPE_IDP_ISSUER=https://id.example.com
For multi-tenant, set rpHostAllowList instead and leave the static RP fields empty:
export NUXT_OPENAPE_IDP_RP_HOST_ALLOW_LIST="id.example.com,id.example.at"
4. Start it
pnpm dev # http://localhost:3003
On first request the database tables and indexes are created. The login page is at /login; the admin UI at /admin (accessible to emails in NUXT_OPENAPE_IDP_ADMIN_EMAILS).
5. Create your first user
Use the management token to mint a registration URL:
curl -X POST http://localhost:3003/api/admin/registration-urls \
-H "Authorization: Bearer $NUXT_OPENAPE_IDP_MANAGEMENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","name":"You"}'
Open the returned URL and register a passkey.
Production build
pnpm --filter openape-free-idp build
node .output/server/index.mjs
Put nginx (or another reverse proxy) in front and terminate TLS there. A minimal vhost:
server {
listen 443 ssl http2;
server_name id.example.com;
ssl_certificate /etc/letsencrypt/live/id.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/id.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3003;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
}
}
The runtime is Node 22+ — the same binary works under systemd, a container, or any Node-capable host.
Migrations
The reference app ships two parallel mechanisms:
- Drizzle-kit migrations under
server/database/migrations/— used in dev to track schema history and generate DDL. - Idempotent startup init in
server/plugins/02.database.ts— the authoritative path in production.CREATE TABLE IF NOT EXISTS+ALTER TABLE ADD COLUMNguarded byPRAGMA table_infolookups. Safe to run on every boot; handles new deploys and schema evolution without a separate migration step.
When you add a schema change, update both: write the Drizzle migration for version control, and mirror the DDL in 02.database.ts so fresh nodes come up cleanly.
Customizing
Every store can be swapped by registering a different factory in server/plugins/04.idp-stores.ts:
import { defineUserStore, defineCredentialStore, … } from '#imports'
export default defineNitroPlugin(() => {
defineUserStore(() => myPostgresUserStore)
defineCredentialStore(() => myPostgresCredentialStore)
// …
})
Common reasons to replace stores:
- Swap libsql for Postgres / DynamoDB / MongoDB.
- Add caching in front of the credential lookup.
- Plug into an existing identity provider during migration.
The CredentialStore, UserStore, CodeStore, etc. interfaces are exported from @openape/auth; implementing each is a few methods per store.
Related
- nuxt-auth-idp — Module options and route reference
- Multi-Tenant IdP — Host multiple domains from one instance
- Auth — Authentication modes and flows