Introduction
Auth Service is a self-hosted OAuth 2.1 / OIDC identity provider built on BetterAuth v1.7. It acts as the central authentication and authorisation hub for a fleet of backend services and frontend applications.

What it provides
- OAuth 2.1 authorization server — authorization code + PKCE, client credentials, token refresh, token introspection, JWKS endpoint, and OIDC discovery.
- Multi-application model — each registered application is an independent OAuth 2.0 client with its own RBAC model, subscription plans, and access policy.
- Per-app RBAC — roles and permissions are defined per application and injected as
roles/permissionsclaims in JWT access tokens when theroles/permissionsscope is requested. - MFA — TOTP (authenticator apps) with backup codes, enforced per application or per user at token-issuance time. The WebAuthn passkey endpoints (
@better-auth/passkey) are enabled on the server; the account page does not offer passkey enrolment yet. - Subscription plans — plans carry a
featuresJSON object that is surfaced as afeaturesclaim in tokens when thefeaturesscope is requested. Optional Stripe integration for billing. - Consumption tracking — a lightweight accounting layer for recording and querying numeric counters (API calls, credits, storage) per user per application. Accepts either a
client_credentialsaccess token bound to one application or an admin session. - Credit wallet — one prepaid euro balance per user, shared by every application and by the LiteLLM gateway: top-up with Stripe or by an administrator, spend reported per call, accounting journal.
- Organizations — multi-tenant support; users belong to organizations and can carry an
org_idclaim in their access tokens. - Social login — Google and GitHub, with a per-application allow-list.
- Login history — tracks every successful login with IP, user-agent, and application context.
- Custom templates — override the login, register, verify-email, email-verified, select-org, two-factor, device and error pages per application via
TEMPLATES_DIR, and the transactional emails globally or per application (when the e-mail has an application context). - Dynamic branding —
APP_NAME+APP_LOGO_URLare served via/api/app-configand drive the frontend UI. - Admin dashboard — Vue 3 SPA with active-users stats, login charts, session management, and user detail views.
- Embedded admin SPA — a Vue 3 frontend served by the service itself (sign-in at
/auth) with Dashboard, Users, Organizations, Applications (roles, permissions and plans are managed per application), Wallets, Accounting and Configuration pages.
When to use auth-service
Auth Service is the right choice when you need:
- A private OAuth 2.1 / OIDC provider with no dependency on a cloud identity service.
- Fine-grained per-application access control that travels inside tokens, so downstream services do not need to query the auth provider on every request.
- A single place to manage users across multiple internal applications (REST APIs, MCP servers, frontends) with different role models.
- MCP server integration — any MCP client can authenticate via OAuth 2.1 Authorization Code + PKCE and receive a JWT access token with roles/permissions/feature claims.
It is not a drop-in replacement for a full IAM platform such as Keycloak or Okta: there is no SAML and no LDAP / Active Directory federation. It prioritises simplicity and tight integration with the CIRCLE Cyber stack over broad protocol coverage. To move an existing application over, see the migration guides for Okta, Auth0 and Keycloak, and the compatibility profiles that make an instance behave like the provider it replaces.
How it fits in
Browser / CLI / MCP client
│
│ OAuth 2.1 authorization code + PKCE
▼
┌─────────────────────┐
│ auth-service │ ← this project
│ (BetterAuth core) │
└────────┬────────────┘
│ JWT access token (roles, permissions, features, org_id)
▼
┌─────────────────────┐
│ backend service │ e.g. IOServer + IOServer-OIDC middleware
│ (resource server) │ verifies token against JWKS, no secret required
└─────────────────────┘
The backend service uses IOServer OIDC (or any JWKS-capable middleware) to verify tokens issued by auth-service. No shared secret is required — validation is done with the public key set published at /api/auth/jwks.
Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js 22, TypeScript (ESM) |
| Package manager | pnpm 10 |
| Web framework | Fastify 5 |
| Auth engine | BetterAuth 1.7 + @better-auth/oauth-provider |
| JWT signing | jwt() plugin (asymmetric; EdDSA / Ed25519 by default, RS256 selectable with JWT_PRIMARY_ALG or a compatibility profile; keys rotated monthly) |
| Passkey / YubiKey | @better-auth/passkey |
| TOTP (Auth app) | twoFactor() from better-auth/plugins |
| Admin / global roles | admin() + role() from better-auth/plugins |
| Organizations | organization() from better-auth/plugins |
| ORM | Drizzle ORM + postgres driver |
| Database | PostgreSQL 17 |
| Frontend | Vue 3 + Vite 8 + Tailwind CSS v4 |
| Testing | Vitest + Supertest + Playwright (E2E) + testcontainers |
| Container | Docker (multi-stage, Node 22 Alpine) |
A tour of the admin interface
All captures use demo data.
Users — roles, MFA status, applications and last login.

Applications — OAuth clients; infrastructure applications are hidden unless asked for.

Configuration — sign-in providers, MFA adoption and billing status.

Account security — every user manages their own sessions and second factor.

Wallets — credit held, pricing of LLM usage, spend and top consumers.

Accounting journal — every top-up, credit adjustment and plan change, with who made it.
