Authentication & Bootstrap
This page covers the full path from first-time setup to production token usage, including Keycloak and Firebase integration.
For environment variables and server.toml options for issuer trust, see OIDC & Issuer Trust and Keycloak.
1. Auth Model
KalamDB uses:
- username/password login at
POST /v1/api/auth/login - JWT bearer tokens for SQL, files, topics, and WebSocket auth
- role-based access (
user,service,dba,system)
For most API routes, use:
Authorization: Bearer <access_token>2. First-Time Server Setup
On a new server, check setup status first:
curl http://127.0.0.1:8080/v1/api/auth/statusWhen setup is required ("needs_setup": true), initialize root and first DBA:
curl -X POST http://127.0.0.1:8080/v1/api/auth/setup \
-H 'Content-Type: application/json' \
-d @- <<'JSON'
{
"username": "admin",
"password": "AdminPass123!",
"root_password": "RootPass123!",
"email": "admin@example.com"
}
JSONImportant behavior:
- setup is localhost-only unless
auth.allow_remote_setup = true - setup does not return tokens; log in afterwards
3. Login And Get Tokens
curl -X POST http://127.0.0.1:8080/v1/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"AdminPass123!"}'Response includes:
access_tokenrefresh_token- token expiry fields
- user metadata
4. Refresh Token Flow
Use bearer or auth cookie:
curl -X POST http://127.0.0.1:8080/v1/api/auth/refresh \
-H "Authorization: Bearer <refresh_or_access_token>"5. Use Token For SQL
curl -X POST http://127.0.0.1:8080/v1/api/sql \
-H "Authorization: Bearer <access_token>" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT CURRENT_USER();"}'6. Role Guidance
user: normal application usersservice: background workers/automationdba: database administrationsystem: highest-privilege internal operations
For worker services consuming topics, use service or higher.
7. External Identity Providers (Keycloak, Firebase, etc.)
How Bearer Token Routing Works
KalamDB inspects every incoming bearer token before signature verification:
- Extract
algfrom the JWT header (unverified). - Extract
issfrom the JWT payload (unverified). - Route to the correct validator:
- HS256 with
iss = "kalamdb"→ internal shared-secret validation usingauth.jwt_secret. - RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 → external OIDC JWKS validation via issuer discovery.
- HS256 with
This means KalamDB natively accepts tokens from external OIDC providers (Keycloak, Auth0, Google, Azure AD, etc.) without a bridge service, as long as:
- The issuer is listed in
auth.jwt_trusted_issuers. - The provider publishes a standard
/.well-known/openid-configurationdocument. - The token is signed with one of the supported algorithms above.
Note: ES512 is not supported.
Option A: Direct OIDC Integration (Recommended)
The simplest approach — KalamDB validates external tokens directly.
Step 1: Configure trusted issuer
[authentication]
jwt_trusted_issuers = "https://keycloak.example.com/realms/myrealm"
auto_create_users_from_provider = trueOr via environment variable:
export KALAMDB_JWT_TRUSTED_ISSUERS="https://keycloak.example.com/realms/myrealm"
export KALAMDB_AUTH_AUTO_CREATE_USERS_FROM_PROVIDER=trueKeep kalamdb in the issuer list if you also use POST /v1/api/auth/login:
export KALAMDB_JWT_TRUSTED_ISSUERS="kalamdb,https://keycloak.example.com/realms/myrealm"Step 2: Create mapped users in KalamDB
CREATE USER 'alice'
WITH OAUTH '{"provider":"keycloak","subject":"keycloak-user-id"}'
ROLE user
EMAIL 'alice@example.com';If auto_create_users_from_provider = true, users are auto-provisioned on first valid token.
Step 3: Use the provider token directly
# Get a token from your IdP (for example, Keycloak direct-access grant)
KC_TOKEN=$(curl -s -X POST \
https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token \
-d "client_id=my-app&grant_type=password&username=alice&password=secret" \
| jq -r .access_token)
# Use it directly with KalamDB
curl -X POST http://127.0.0.1:8080/v1/api/sql \
-H "Authorization: Bearer $KC_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT CURRENT_USER();"}'How OIDC Discovery Works
When KalamDB encounters an external token:
- Fetches
{issuer_url}/.well-known/openid-configuration. - Extracts the
jwks_urifrom the discovery document. - Fetches the JWKS and caches keys by
kid. - Matches the token’s
kidheader to a cached key. - On cache miss (unknown
kid), automatically refreshes the JWKS cache. - Validates signature,
iss,exp, andaud(ifclient_idis configured).
JWKS caching: Keys are cached per-issuer in memory. There is no TTL-based eviction — cache refreshes only on
kidmiss. Keys without akidfield are discarded.
JWT Claims Used by KalamDB
| Claim | Type | Required | Notes |
|---|---|---|---|
sub | string | Yes | User subject identifier |
iss | string | Yes | Must match a trusted issuer |
exp | number | Yes | Unix timestamp (seconds) |
iat | number | Yes | Unix timestamp (seconds) |
username / preferred_username | string | No | Maps to KalamDB username (serde alias) |
email | string | No | User email |
role | string | No | KalamDB role (user, service, dba, system) |
token_type | string | No | "access" or "refresh" |
For full OIDC configuration details, see OIDC & Issuer Trust. For Keycloak-specific setup, see Keycloak.
Option B: Token Exchange / Bridge Pattern
Use this when your IdP does not publish OIDC discovery, or you need custom claim mapping.
Bridge service validates provider tokens, then mints KalamDB-compatible HS256 tokens:
import { createRemoteJWKSet, jwtVerify } from 'jose';
import jwt from 'jsonwebtoken';
const keycloakJWKS = createRemoteJWKSet(
new URL('https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs')
);
async function exchangeKeycloakToken(idToken: string) {
const { payload } = await jwtVerify(idToken, keycloakJWKS, {
issuer: 'https://keycloak.example.com/realms/myrealm',
audience: 'my-client-id',
});
return jwt.sign(
{
sub: String(payload.sub),
iss: 'kalamdb-keycloak-bridge',
username: 'alice',
role: 'user',
email: payload.email,
token_type: 'access',
},
process.env.KALAMDB_JWT_SECRET!,
{ algorithm: 'HS256', expiresIn: '1h' }
);
}Firebase example (bridge required — Firebase does not use standard OIDC discovery):
import admin from 'firebase-admin';
import jwt from 'jsonwebtoken';
async function exchangeFirebaseToken(idToken: string) {
const decoded = await admin.auth().verifyIdToken(idToken);
return jwt.sign(
{
sub: decoded.uid,
iss: 'kalamdb-firebase-bridge',
username: 'mobile_alice',
role: 'user',
email: decoded.email,
token_type: 'access',
},
process.env.KALAMDB_JWT_SECRET!,
{ algorithm: 'HS256', expiresIn: '1h' }
);
}Use the exchanged token:
curl -X POST http://127.0.0.1:8080/v1/api/sql \
-H "Authorization: Bearer <exchanged_kalamdb_token>" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT CURRENT_USER();"}'The bridge issuer (e.g. kalamdb-keycloak-bridge) must appear in jwt_trusted_issuers.
8. Production Hardening
Before exposing KalamDB publicly:
- Set strong
auth.jwt_secret(orKALAMDB_JWT_SECRET). - Keep
auth.allow_remote_setup = false. - Set
auth.cookie_secure = truebehind HTTPS. - Configure
auth.jwt_trusted_issuerswhen using external IdPs. - Tune
rate_limit.max_auth_requests_per_ip_per_sec.
For full endpoint auth behavior, see HTTP API Reference.