A provider-agnostic, API-first identity platform for Hilbras products and third-party applications.
Keystone is a standalone identity platform, not a wrapper around another identity system. It authenticates users, issues signed tokens, enforces authorization, audits every security decision, and federates identities from any OIDC provider.
- Identity Provider (IdP) — OIDC/OAuth2 provider with JWKS discovery.
- Authentication Service — email/password, social login, magic links, WebAuthn/Passkeys, TOTP, SMS OTP.
- Authorization Engine — RBAC/ABAC permissions with
/v1/authz/check. - Token Authority — short-lived JWT access tokens, rotating refresh tokens, opaque API keys.
- Machine Identity Manager — scoped, rotatable, auditable service credentials.
- Federation Broker — delegate login to Google, GitHub, Azure, Okta, Keycloak, Zitadel, or any OIDC provider and issue Keystone tokens.
- Audit & Compliance — immutable audit log, event bus, webhooks, and anomaly detection.
- Workflow Platform — configurable post-auth workflows (assign roles, create orgs, send email, fire webhooks).
- Simplified browser setup wizard — choose a profile (Development, Docker Compose, Production), test PostgreSQL/Redis, create the owner account, and connect your first project without editing files.
- Setup diagnostics & dry-run — validate the full configuration before applying it, then run a health report after setup completes.
- Mobile-first admin UI — the dashboard and setup wizard are usable down to 375px widths, with touch-friendly controls and hash-routed tabs.
- Security dashboard — owners can view 24h logins, failed logins, active sessions, MFA adoption, and recent login activity.
- Account lockout protection — repeated failed logins temporarily lock accounts and emit security events.
- Azure Key Vault secrets provider — store JWT signing and encryption keys in Azure Key Vault in addition to the default database provider.
- API key scopes & signed webhooks — API keys carry granular scopes and audit webhook deliveries are signed with HMAC.
- One-click project connection — copy integration snippets for React, Next.js, Angular, Svelte, Vue, Django, Rails, Go, Python, and plain HTML.
- End-to-end test suite — Playwright tests cover the simple setup wizard and the post-setup security dashboard using an isolated
hilbras_testdatabase.
┌─────────────────────────────────────────────────────────────────────────┐
│ Consumers │
│ Web App Mobile CLI Microservice External SaaS │
└──────┬───────┬─────────┬──────┬──────────────┬──────────────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Public SDK Layer │
│ JS / React / Next.js / Python / CLI │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Hilbras Keystone │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ API-First Admin & Public APIs │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Internal SDK (stable contracts) │ │
│ │ AuthenticationSdk │ IdentitySdk │ OrganizationSdk │ AuthzSdk │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Application Services (use cases / HTTP agnostic) │ │
│ │ AuthenticationApplicationService │ OrganizationApplication... │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Domain Services (business rules) │ │
│ │ AuthenticationDomainService │ AuthorizationDomainService │ │
│ │ IdentityDomainService │ OrganizationDomainService │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Repositories (persistence abstraction) │ │
│ │ UserRepository │ OrganizationRepository │ ApplicationRepository │ │
│ │ IdentityRepository │ AuditRepository │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ Identity │ │ Token │ │ Authorization │ │ │
│ │ Connectors │ │ Service │ │ Engine │ │ │
│ └──────────────┘ ┌──────────────┘ └──────────────────────┘ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ Versioned │ │ Secrets │ │ Workflow Engine │ │ │
│ │ Event Bus │ │ Provider │ │ + Background Queue │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │
│ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ DI Container │ Plugin Registry │ ConfigurationService │ │ │
│ └────────────────────────────────────────────────────────────┘ │ │
└─────────────────────────────────────────────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
PostgreSQL Redis External providers
Identity connectors know only how to talk to an external provider:
- Build authorization URLs
- Exchange codes for tokens
- Validate identity tokens
- Retrieve and normalize profile information
They do not create users, link identities, issue Keystone tokens, or manage sessions. Those responsibilities live in higher-level services such as FederationService and AuthenticationService. This keeps connectors small, reusable, and easy to test.
Routes delegate to an Application Layer that executes use cases, which in turn delegate to Domain Services that encode business rules. Domain services depend on Repository interfaces, not on SQL or ORM details.
Routes → Application Services → Domain Services → Repositories
This keeps business logic independent of HTTP and makes future transports (CLI, gRPC, workers, GraphQL) straightforward.
The audit event bus emits structured events such as user_registered, user_login, oauth_callback, api_key_created, and authz_check.
Every event carries a version:
{
"type": "user.login",
"version": 1,
"timestamp": "2026-07-13T00:00:00Z",
"payload": {
"userId": "...",
"ip": "...",
"metadata": {}
}
}Versioned events let subscribers evolve independently without breaking integrations.
The in-process event bus is a fast starting point, but actions such as sending emails, delivering webhooks, running analytics, and executing workflow steps should move to a background job queue. Keystone ships with a BullMQ-backed queue that is used automatically when Redis is available (KEYSTONE_QUEUE_PROVIDER="" or bullmq), falling back to an in-process queue for local development.
Secrets management is abstracted so Keystone can store and rotate keys in different backends:
DatabaseSecretsProvider— default, stores keys in PostgreSQLEnvironmentSecretsProvider— read from env varsAWS KMS Provider,HashiCorp Vault Provider,Azure Key Vault Provider— enterprise options (via plugin or future built-in providers)
The default provider handles JWT signing keys, encryption keys, password hashes, API keys, and client secrets with rotation support. JWT keys are rotated with a 24-hour grace period so tokens signed with the previous key remain valid.
Keystone is designed to be extended without touching core code. The plugin registry can register:
- Identity providers and authentication methods
- Email and SMS providers
- Custom workflow steps
- Analytics, billing, and custom authorization policies
Load plugins at startup via KEYSTONE_PLUGINS=./plugins/my-plugin.js or call app.registerPlugin(plugin) at runtime. Each plugin exports a KeystonePlugin object with optional connectors, emailProvider, smsProvider, and workflowSteps.
Routes, CLI commands, workers, and scheduled jobs share a stable internal client layer instead of calling low-level services directly:
import { getSdk } from "./sdk/index.js";
const sdk = getSdk();
const session = await sdk.authentication.login({ email, password });
const user = await sdk.identity.findUser(session.user.id);
const allowed = await sdk.authorization.hasPermission(role, resource, action);
const org = await sdk.organization.createOrganization(userId, { name: "Acme" });The SDK exposes stable TypeScript interfaces (AuthenticationSdk, IdentitySdk, OrganizationSdk, AuthorizationSdk) while hiding the concrete application and domain service implementations.
A lightweight DI container wires repositories, domain services, and application services. The container is initialized when the app boots:
import { initializeContainer, getContainer } from "./di.js";
initializeContainer();
const users = getContainer().userRepository;Services receive dependencies through constructors, making unit testing with mocked repositories straightforward.
Domain services depend on repository interfaces such as UserRepository, OrganizationRepository, ApplicationRepository, IdentityRepository, and AuditRepository. Drizzle-based implementations live in src/repositories/, but the persistence layer can be swapped without touching business logic.
Internal services return a uniform Result<T> type instead of throwing:
import { ok, err, type Result } from "./lib/result.js";
function findUser(id: string): Result<User> {
if (!user) return err({ code: "NOT_FOUND", message: "User not found", statusCode: 404 });
return ok(user);
}This simplifies error handling and reduces duplicated try/catch logic.
ConfigurationService centralizes loading, defaults, validation, and environment-specific overrides. Access runtime configuration through the container or via config.get().
Enable or disable functionality at runtime through KEYSTONE_FEATURE_FLAGS:
KEYSTONE_FEATURE_FLAGS=beta_oauth=true,experimental_workflows=falseCheck flags in code:
const features = getContainer().features;
if (features.isEnabled("beta_oauth")) { /* ... */ }- Runtime: Fastify 5 + TypeScript
- Database: Drizzle ORM +
postgres - Cache / rate limiting: Redis (ioredis)
- Tokens & JWKS:
jose - Validation:
zod - Password hashing:
argon2idwith legacyscryptverification - WebAuthn:
@simplewebauthn/server - Tracing: OpenTelemetry
From Hilbras/Keystone:
./install.sh # installs backend + frontend deps + Playwright ChromiumLaunch Keystone. start.sh will automatically start PostgreSQL and Redis via Docker if they are not already running. If no .env exists (or DATABASE_URL is missing), Keystone starts in setup mode and opens the browser wizard instead of the main API:
./start.sh # starts backend/setup server + frontend togetherThe wizard guides you through:
- Pasting the setup token printed in the server logs.
- Choosing an environment profile (Development, Docker Compose, Production).
- Configuring and testing PostgreSQL and Redis.
- Setting public URLs and allowed origins (advanced mode).
- Generating platform secrets (internal API key, encryption key).
- Choosing and testing email and SMS providers (advanced mode).
- Enabling optional identity connectors (advanced mode).
- Creating the first owner account and connecting your first project.
- Reviewing diagnostics and completing setup.
When finished, the wizard writes .env, runs migrations, creates the owner, and optionally restarts the server in normal API mode.
After setup, the same UI at http://localhost:5173 becomes the admin dashboard. Log in with the owner email and password to view:
- Overview — API health and OIDC discovery endpoints.
- Organizations — all platform organizations.
- Applications — all registered OAuth/OIDC applications.
- Connect Project — copy integration snippets for React, Next.js, Angular, Svelte, Vue, Django, Rails, Go, Python, and HTML.
- Users — all platform users.
- Security — 24h logins, failed logins, active sessions, MFA adoption, and recent activity.
- Audit Logs — recent security events.
- Workflows — configurable post-automation flows.
- Settings — platform configuration and feature flags.
If you prefer to manage each part separately:
- Install backend dependencies:
npm install - Install frontend dependencies:
cd frontend && npm install - Start Postgres and Redis.
- Copy
.env.exampleto.envand fill in the required values:DATABASE_URLREDIS_URLAUTH_API_PUBLIC_URLCLIENT_APP_URLALLOWED_ORIGINSKEYSTONE_INTERNAL_API_KEYKEYSTONE_ENCRYPTION_KEY(optional in dev)- Zitadel is optional. If you want to use it, set
ZITADEL_DOMAIN,ZITADEL_CLIENT_ID, andZITADEL_CLIENT_SECRET. - To enable Google/GitHub/Azure/Okta/Keycloak federation, set their
*_CLIENT_IDand*_CLIENT_SECRETvariables.
- Run migrations:
npm run db:migrate - Start the backend:
npm run dev - In another terminal, start the frontend:
cd frontend && npm run dev
Start test services with Docker Compose:
docker compose -f docker-compose.test.yml up -d
npm testBefore committing or releasing, run the full build and type check:
npm run build:all
npm run typecheck:allThe setup wizard and post-setup dashboard are tested with Playwright. The E2E suite uses an isolated hilbras_test database that is reset automatically before each run. From the project root:
npm run test:allOr run only the backend tests or only the E2E tests:
npm test # backend tests
cd frontend && npm run test:e2e # Playwright E2E testsnpm ci
npm run build
npm startnpm run build compiles TypeScript and copies migration SQL files into dist/db/migrations so they are available at runtime.
-
Copy and customize the environment file:
cp .env.example .env # Edit .env with production secrets, URLs, and provider credentials. -
Start the stack:
docker compose up -d
This launches PostgreSQL, Redis, and the Keystone API container with health checks and restart policies. On first run, set
KEYSTONE_SETUP_MODE=truein.envand visithttp://localhost:4001/setupto complete the browser wizard. -
View logs:
docker compose logs -f keystone
-
Restart after configuration changes:
docker compose down && docker compose up -d
For hosts running Docker with systemd, install the provided unit file:
sudo cp scripts/keystone.service /etc/systemd/system/hilbras-keystone.service
sudo systemctl daemon-reload
sudo systemctl enable --now hilbras-keystonePlace the project files in /opt/hilbras-keystone and ensure .env is present there.
For high-availability deployments, replace the bundled postgres and redis services with managed instances and update DATABASE_URL and REDIS_URL accordingly. You can then run Keystone with a minimal compose file:
services:
keystone:
build: .
ports:
- "4001:4001"
env_file: .env| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
REDIS_URL |
Redis connection string |
AUTH_API_PUBLIC_URL |
Public URL used for discovery and redirects |
CLIENT_APP_URL |
Default redirect URL after login |
ALLOWED_ORIGINS |
Comma-separated CORS origins |
KEYSTONE_INTERNAL_API_KEY |
Secret for service-to-service calls |
KEYSTONE_ENCRYPTION_KEY |
Master key for encrypted secrets (optional in dev) |
JWT_PRIVATE_KEY / JWT_PUBLIC_KEY |
RSA key pair for signing tokens (optional in dev) |
ZITADEL_DOMAIN |
Zitadel instance domain (optional connector) |
ZITADEL_CLIENT_ID / ZITADEL_CLIENT_SECRET |
Zitadel OIDC app credentials |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Google connector credentials |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
GitHub connector credentials |
AZURE_CLIENT_ID / AZURE_CLIENT_SECRET |
Azure connector credentials |
OKTA_ISSUER / OKTA_CLIENT_ID / OKTA_CLIENT_SECRET |
Okta connector credentials |
KEYCLOAK_ISSUER / KEYCLOAK_CLIENT_ID / KEYCLOAK_CLIENT_SECRET |
Keycloak connector credentials |
EMAIL_PROVIDER |
none, console, smtp, sendgrid, or mailgun |
EMAIL_FROM |
Default sender address |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_SECURE |
SMTP settings |
SENDGRID_API_KEY |
SendGrid API key |
MAILGUN_API_KEY / MAILGUN_DOMAIN |
Mailgun settings |
SMS_PROVIDER |
none, console, or twilio |
TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_FROM_NUMBER / TWILIO_MESSAGING_SERVICE_SID |
Twilio settings |
AUDIT_WEBHOOK_URL |
Webhook destination for audit events |
AUDIT_CONSOLE_EXPORT |
true to log audit events to console |
KEYSTONE_SEED_OWNER_EMAIL |
Default owner email for seeded organization |
KEYSTONE_SEED_OWNER_PASSWORD |
Password for the seed owner |
KEYSTONE_SECRETS_PROVIDER |
database (default) or environment |
AZURE_KEY_VAULT_URL |
Azure Key Vault URL for the azure-key-vault secrets provider |
AZURE_KEY_VAULT_TENANT_ID / AZURE_KEY_VAULT_CLIENT_ID / AZURE_KEY_VAULT_CLIENT_SECRET |
Azure service principal credentials |
KEYSTONE_QUEUE_PROVIDER |
in-process, bullmq, or empty to auto-select when Redis is available |
KEYSTONE_PLUGINS |
Comma-separated module paths of plugins to load at startup |
KEYSTONE_FEATURE_FLAGS |
Comma-separated `flag=true |
See docs/INTEGRATION.md for complete examples covering:
- React / Next.js / SPAs (OIDC + PKCE)
- Backend services and microservices (API keys)
- Python / FastAPI token verification
- Mobile apps (system browser + custom URL scheme)
- CLI scripts
- Federation through external IdPs
If you already have a login/signup page with email/password and Google login and just want to wire it up, start with docs/LOGIN_FORM_INTEGRATION.md or load the one-line CDN script from http://localhost:4001/sdk/keystone-dropin.js.
For production deployment with HTTPS and a custom domain, see docs/DEPLOYMENT.md.
If Keystone feels slow or uses a lot of memory during development, see docs/PERFORMANCE.md.
| Method | Path | Description |
|---|---|---|
| POST | /auth/register |
Email/password signup (local password hashing) |
| POST | /auth/login |
Email/password login (sets session cookies) |
| POST | /auth/token-login |
Email/password login for SPA/dashboard (returns bearer token) |
| POST | /auth/logout |
Revoke session |
| GET | /auth/me |
Current user |
| POST | /auth/refresh |
Rotate refresh token |
| GET | /auth/oauth/:provider |
Start OAuth login (google, github, azure, okta, keycloak, zitadel) |
| GET | /auth/callback/:provider |
OAuth callback |
| POST | /auth/forgot-password |
Request password reset |
| POST | /auth/reset-password |
Complete password reset |
| POST/GET/DELETE | /auth/api-keys |
Personal API keys |
| GET | /auth/validate |
Internal token/API-key validation |
| Method | Path | Description |
|---|---|---|
| GET | /federation/providers |
List supported federation providers |
| GET | /federation/:provider/start |
Start broker login through an external IdP |
| GET | /federation/:provider/callback |
Broker callback; issues Keystone tokens |
| POST | /federation/link |
Link an external identity to the current user |
| GET | /federation/identities |
List linked external identities |
| Method | Path | Description |
|---|---|---|
| GET | /oauth2/authorize |
Authorization endpoint (PKCE required) |
| POST | /oauth2/token |
Token endpoint |
| GET | /oauth2/userinfo |
UserInfo endpoint |
| POST | /oauth2/revoke |
Token revocation |
| POST | /oauth2/consent |
Grant or revoke consent |
| Method | Path | Description |
|---|---|---|
| POST/GET | /v1/admin/organizations |
Create / list organizations for the current user |
| GET | /v1/admin/organizations/:id |
Organization details |
| POST/GET | /v1/admin/organizations/:id/applications |
Create / list apps |
| PATCH | /v1/admin/organizations/:id/applications/:appId |
Update app |
| POST/GET | /v1/admin/organizations/:id/invites |
Invite / list invites |
| GET | /v1/admin/organizations/:id/members |
List members |
| PATCH/DELETE | /v1/admin/organizations/:id/members/:userId |
Update / remove member |
| GET | /v1/admin/organizations/:id/users |
List users in org |
| GET/PATCH/DELETE | /v1/admin/organizations/:id/users/:userId |
Manage user |
| GET | /v1/admin/permissions |
List all permissions |
| GET/POST/DELETE | /v1/admin/roles/:role/permissions |
Manage role permissions |
| GET/DELETE | /v1/admin/organizations/:id/api-keys |
Org-scoped API keys |
| GET | /v1/admin/organizations/:id/audit-logs |
Paginated audit logs |
| GET | /v1/admin/platform/users |
Owner only — list all users |
| GET | /v1/admin/platform/organizations |
Owner only — list all organizations |
| GET | /v1/admin/platform/applications |
Owner only — list all applications |
| GET | /v1/admin/platform/audit-logs |
Owner only — list recent audit logs |
| Method | Path | Description |
|---|---|---|
| GET | /.well-known/openid-configuration |
OIDC discovery document |
| GET | /.well-known/jwks.json |
Public keys for token verification |
| GET | /health |
Health check |
| GET | /metrics |
Prometheus metrics |
| GET | /documentation |
OpenAPI/Swagger UI |
# Rotate the active JWT signing key
npx keystone secrets:rotate
# Generate a JWT key pair for env vars
npx keystone keys:create
# List active JWT signing keys
npx keystone keys:list
# Run migrations
npx keystone migrate
# Validate required configuration
npx keystone config:validate
# Create the first platform owner
npx keystone user:create --email admin@example.com --password 'Str0ngP@ss!' --role owner
# Create an organization from the command line
npx keystone org:create --name "Acme" --owner-email admin@example.com- Passwords are never stored in plaintext.
- Tokens and secrets are hashed at rest.
- JWTs are signed with RS256 and keys are rotatable.
- Rate limiting is applied to every public endpoint.
- Every authentication decision is audited.
- Cookies use
HttpOnly,Secure, andSameSite. - OAuth2 public clients must use PKCE.
Full documentation lives in docs/:
| Document | Purpose |
|---|---|
| API reference | Every HTTP endpoint with auth requirements |
| Architecture | Layered design, services, events, plugins |
| Deployment | Docker Compose, Kubernetes, systemd, hardening |
| Integration guide | Connect your apps to Keystone |
| Security model | Threat model and hardening checklist |
| Performance | Tuning and load-testing notes |
| Contributing | Dev environment and PR process |
| Roadmap | Planned work |
| ADRs | Architecture decision records |
| Changelog | Release history |
The running API also serves interactive docs at /documentation (Swagger UI).
MIT — Hilbras engineering. See LICENSE.