Agora

Deployment Topologies

Run a dedicated IdP, or embed the provider inside an existing app.

AuthKit is topology-agnostic: the same server config works whether the Authorization Server is its own deployment or lives inside one of your product apps.

Standalone

A dedicated identity provider app — nothing but AuthKit. Product apps are all relying parties pointing at it. This is a dedicated auth app.

config/authkit.ts (standalone IdP)
const authServerConfig = defineConfig({
  issuer: env.get('AUTHKIT_ISSUER'), // https://auth.acme.com/oidc
  adapter: adapters.database({ connection: 'auth' }),
  jwks: { source: 'managed', algorithm: 'RS256' },
  accountStore: lucidAccountStore(AuthUser),
  mountPath: '/oidc',
  render: inertiaRenderer({ prefix: 'authkit' }),
  admin: { enabled: true },
  adminApi: { enabled: true, apiKeys: [env.get('AUTHKIT_ADMIN_API_KEY')] },
})

The IdP owns the user identities. After first boot, create each client (acme-web, acme-admin, …) in the admin console at /admin/clients or via the Admin REST API:

curl -X POST https://auth.acme.com/api/authkit/v1/clients \
  -H "Authorization: Bearer $AUTHKIT_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "clientId": "acme-web",
        "redirectUris": ["https://web.acme.com/auth/callback"],
        "postLogoutRedirectUris": ["https://web.acme.com/"],
        "grantTypes": ["authorization_code", "refresh_token"],
        "tokenEndpointAuthMethod": "client_secret_basic"
      }'

Each client gets its own clientId/clientSecret and registered redirect URIs. The secret is shown once in the response — save it in the client app's .env.

Embedded

The provider runs inside an existing product app, and that app is also one of its own clients (host-is-its-own-client). This is the acme-web example: the same process serves the product and the OIDC endpoints.

config/authkit.ts (embedded)
const authServerConfig = defineConfig({
  issuer: env.get('AUTHKIT_ISSUER'),
  adapter: adapters.database({ connection: 'auth' }),
  jwks: { source: 'managed', algorithm: 'RS256' },
  accountStore: lucidAccountStore(AuthUser),
  mountPath: '/oidc',
  render: inertiaRenderer({ prefix: 'authkit' }),
  admin: { enabled: true },
  adminApi: { enabled: true, apiKeys: [env.get('AUTHKIT_ADMIN_API_KEY')] },
})

After first boot, create the host app's own client (acme-web) in the admin console or API, then copy the returned clientSecret to .env. Alongside the server config, add a config/authkit_client.ts pointing at the same issuer — the host authenticates against itself. See Client.

mountPath, issuer & cookies

  • mountPath (default /oidc) is where the provider's wildcard routes are mounted. It must match the tail of issuer.
  • issuer is the public, externally-reachable URL — scheme://host[:port]/mountPath. Relying parties derive /auth, /token, /jwks, and /session/end from it.
  • Cookies — the provider sets its SSO session cookies on the issuer's host. In the embedded topology the provider and the host app share an origin, so the IdP session and the app session coexist on the same host (two separate cookies). Provide signing keys via cookieKeys if you want stable cookie signatures across restarts.

Switching topologies later is mostly an infrastructure change — split the app out, keep the same issuer URL, and the relying parties don't need to change.

On this page