Skip to content

ZeptoDB SSO Integration Guide

This guide covers connecting ZeptoDB to OIDC identity providers for Single Sign-On.


The simplest setup — one flag auto-configures everything:

Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://dev-123456.okta.com/oauth2/default \
--oidc-client-id 0oa1bcdef2ghijk3l4m5 \
--oidc-client-secret YOUR_CLIENT_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback

ZeptoDB fetches /.well-known/openid-configuration and auto-configures:

  • JWKS endpoint (key rotation)
  • Authorization endpoint (SSO login redirect)
  • Token endpoint (code exchange)

Users can then:

  1. Open the Web UI → click “Sign in with SSO”
  2. Authenticate at the IdP
  3. Get redirected back with a session cookie

ProviderOIDC DiscoveryGroup ClaimTested
Oktagroups
Azure AD (Entra ID)groups (GUID) or roles
Google WorkspaceN/A (use directory API)
Keycloakgroups or realm_access.roles
Auth0https://zeptodb.com/roles (custom)
AWS Cognitocognito:groups

  1. In Okta Admin → Applications → Create App Integration
  2. Select “OIDC - OpenID Connect” → “Web Application”
  3. Configure:
    • Sign-in redirect URI: http://localhost:8123/auth/callback
    • Sign-out redirect URI: http://localhost:8123/login
    • Assignments: assign users/groups
  4. Note the Client ID and Client Secret
  5. Enable Groups claim:
    • Security → API → Authorization Servers → default → Claims
    • Add claim: name=groups, value type=Groups, filter=Matches regex .*
Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://dev-123456.okta.com/oauth2/default \
--oidc-client-id 0oa1bcdef2ghijk3l4m5 \
--oidc-client-secret YOUR_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback

Group mapping (in code or config):

ZeptoDB-Admins → admin
Trading-Desk → writer
Quant-Research → reader
  1. Azure Portal → App registrations → New registration
  2. Redirect URI: http://localhost:8123/auth/callback (Web)
  3. Certificates & secrets → New client secret
  4. Token configuration → Add groups claim → Security groups
  5. Note: Azure sends group GUIDs, not names. Map GUIDs to roles.
Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://login.microsoftonline.com/TENANT_ID/v2.0 \
--oidc-client-id APP_CLIENT_ID \
--oidc-client-secret APP_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback \
--oidc-audience api://APP_CLIENT_ID

Group mapping (Azure uses GUIDs):

a1b2c3d4-... → admin # "ZeptoDB Admins" group GUID
e5f6g7h8-... → writer # "Trading Desk" group GUID
  1. Google Cloud Console → APIs & Services → Credentials → Create OAuth client ID
  2. Application type: Web application
  3. Authorized redirect URI: http://localhost:8123/auth/callback
  4. Enable “Google Workspace” domain-wide delegation if needed
Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://accounts.google.com \
--oidc-client-id CLIENT_ID.apps.googleusercontent.com \
--oidc-client-secret GOCSPX_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback

Note: Google does not include groups in the ID token by default. Use zepto_role custom claim via Google Workspace Admin SDK, or assign roles via the ZeptoDB API key system alongside SSO.

  1. Keycloak Admin → Clients → Create client
  2. Client type: OpenID Connect
  3. Valid redirect URIs: http://localhost:8123/auth/callback
  4. Client authentication: On (confidential)
  5. Add group mapper: Client scopes → dedicated scope → Add mapper → Group Membership
    • Token claim name: groups
    • Full group path: Off
Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://keycloak.example.com/realms/zeptodb \
--oidc-client-id zeptodb \
--oidc-client-secret KEYCLOAK_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback
  1. Auth0 Dashboard → Applications → Create Application → Regular Web Application
  2. Allowed Callback URLs: http://localhost:8123/auth/callback
  3. Create a Rule or Action to add roles to the token:
// Auth0 Action: Add roles to ID token
exports.onExecutePostLogin = async (event, api) => {
const roles = event.authorization?.roles || [];
api.idToken.setCustomClaim('https://zeptodb.com/roles', roles);
};
Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://YOUR_DOMAIN.auth0.com/ \
--oidc-client-id AUTH0_CLIENT_ID \
--oidc-client-secret AUTH0_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback
  1. Cognito → User Pools → Create/select pool
  2. App integration → Create app client (Confidential client)
  3. Hosted UI: add callback URL http://localhost:8123/auth/callback
  4. Create groups in Cognito (e.g., zeptodb-admins, zeptodb-writers)
Terminal window
./zepto_http_server --port 8123 \
--oidc-issuer https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOLID \
--oidc-client-id COGNITO_CLIENT_ID \
--oidc-client-secret COGNITO_SECRET \
--oidc-redirect-uri http://localhost:8123/auth/callback

Cognito group claim: cognito:groups


Section titled “Flow 1: Web UI SSO (recommended for humans)”
Browser → GET /auth/login
→ 302 Redirect to IdP
→ User authenticates at IdP
→ 302 Redirect to /auth/callback?code=...
→ Server exchanges code for tokens
→ Server creates session (Set-Cookie: zepto_sid=...)
→ 302 Redirect to /query
→ Subsequent requests use session cookie

Flow 2: JWT Bearer Token (for APIs/scripts)

Section titled “Flow 2: JWT Bearer Token (for APIs/scripts)”
Client obtains JWT from IdP (e.g., client_credentials grant)
Client → POST / -H "Authorization: Bearer eyJ..."
→ Server validates JWT signature via JWKS
→ Server resolves identity (issuer routing → group mapping)
→ Query executes with resolved role/permissions
Admin creates key → POST /admin/keys {"name":"svc","role":"writer"}
Service → POST / -H "Authorization: Bearer zepto_..."
→ Server validates key hash
→ Query executes with key's role

ZeptoDB maps IdP groups to its 5-role model:

ZeptoDB RolePermissionsTypical IdP Group
adminFull access (DDL, queries, user management)ZeptoDB-Admins
writerRead + write (SQL + ingest)Trading-Desk, Data-Engineers
readerSELECT queries onlyQuant-Research, Analysts
analystSELECT, restricted to symbol whitelistExternal-Analysts
metrics/metrics, /health, /stats onlyMonitoring-Service

When a user belongs to multiple groups, the highest-privilege role wins.


After SSO login, ZeptoDB issues an HttpOnly session cookie instead of exposing the raw JWT to the browser.

SettingDefaultDescription
session.ttl_s3600Session lifetime (seconds)
session.refresh_window_s300Extend session if active within this window
session.max_sessions10000Max concurrent sessions
session.cookie_namezepto_sidCookie name
session.cookie_securefalseSet true when TLS enabled
session.cookie_httponlytruePrevent JavaScript access

If the IdP issued a refresh token, ZeptoDB stores it in the session. The Web UI calls POST /auth/refresh before the session expires to get a new access token without re-authentication.


MethodPathAuthDescription
GET/auth/loginPublicRedirect to IdP authorization endpoint
GET/auth/callbackPublicOAuth2 code exchange → session → redirect
POST/auth/sessionBearerCreate session from existing token
POST/auth/logoutPublicDestroy session, clear cookie
POST/auth/refreshCookieRefresh token using stored refresh_token
GET/auth/meCookie/BearerReturn current identity
POST/admin/auth/reloadAdminForce JWKS key refresh
Terminal window
# With session cookie
curl -b "zepto_sid=abc123" http://localhost:8123/auth/me
# With Bearer token
curl -H "Authorization: Bearer eyJ..." http://localhost:8123/auth/me
{"subject": "user@example.com", "role": "writer", "source": "sso:okta-prod"}

For environments with multiple identity providers (e.g., internal Okta + external Azure AD):

AuthManager::Config cfg;
cfg.sso_enabled = true;
// Internal employees via Okta
IdpConfig okta;
okta.id = "okta-internal";
okta.issuer = "https://corp.okta.com/oauth2/default";
okta.jwks_url = "https://corp.okta.com/oauth2/default/v1/keys";
okta.group_role_map = {{"Admins", Role::ADMIN}, {"Traders", Role::WRITER}};
cfg.sso_idps.push_back(okta);
// External partners via Azure AD
IdpConfig azure;
azure.id = "azure-partners";
azure.issuer = "https://login.microsoftonline.com/TENANT/v2.0";
azure.jwks_url = "https://login.microsoftonline.com/TENANT/discovery/v2.0/keys";
azure.default_role = Role::ANALYST; // restricted by default
cfg.sso_idps.push_back(azure);

Token routing is automatic: the JWT iss claim determines which IdP config is used.


SSO client secrets should not be stored in plaintext config files. Use the secrets provider chain:

Terminal window
# Store OIDC client secret in Vault
vault kv put secret/zeptodb/oidc client_secret=YOUR_SECRET
# ZeptoDB reads from Vault at startup
./zepto_http_server \
--vault-addr https://vault.example.com \
--vault-token $VAULT_TOKEN \
--vault-path secret/data/zeptodb/oidc \
--oidc-issuer https://dev-123456.okta.com/oauth2/default \
--oidc-client-id 0oa1bcdef2ghijk3l4m5
apiVersion: v1
kind: Secret
metadata:
name: zeptodb-oidc
type: Opaque
stringData:
client-secret: YOUR_SECRET
---
# In ZeptoDB deployment
env:
- name: ZEPTO_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: zeptodb-oidc
key: client-secret
Terminal window
export ZEPTO_OIDC_CLIENT_SECRET=YOUR_SECRET
./zepto_http_server --oidc-issuer ... --oidc-client-id ...

Priority chain: Vault → K8s Secret → Environment Variable → Config file


SymptomCauseFix
”OIDC not configured” on /auth/login--oidc-issuer not set or discovery failedCheck issuer URL, verify /.well-known/openid-configuration is accessible
”Token exchange failed” on callbackClient secret wrong or redirect URI mismatchVerify client_secret and redirect_uri match IdP config exactly
”SSO: no matching IdP”JWT issuer doesn’t match any registered IdPCheck iss claim in JWT matches IdpConfig::issuer
Groups not mapped to rolesGroup claim name mismatchCheck IdP’s group claim name (e.g., groups vs cognito:groups)
Session expires too quicklyDefault TTL is 1 hourIncrease session.ttl_s
JWKS refresh failsNetwork/firewall blocking JWKS URLEnsure server can reach IdP’s JWKS endpoint
”Invalid or expired JWT”Clock skew between server and IdPSync NTP, or the token is genuinely expired
Terminal window
# Decode JWT payload (no verification)
echo "eyJ..." | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
Terminal window
curl -X POST http://localhost:8123/admin/auth/reload \
-H "Authorization: Bearer $ADMIN_KEY"

  • Always use HTTPS in production (--tls-cert, --tls-key)
  • Set session.cookie_secure=true when TLS is enabled
  • Store client secrets in Vault or K8s Secrets, never in config files
  • Use short session TTLs (1h) with refresh tokens for long-lived sessions
  • Enable audit logging to track all SSO authentication events
  • Restrict redirect_uri to exact match (no wildcards) in IdP config