Auth Service — Security Model
Status: populated Owner: Security Last updated: 2026-04-19
Change log
- v1.2 (2026-04-19) — Rebaselined to Keycloak as the base/default IdP with a pluggable IdP provider abstraction. Documented OIDC and SAML 2.0 federation for tenant external IdP SSO. Firebase downgraded to an optional legacy provider.
1. Authentication — per provider
auth-service delegates the actual credential verification to the provider bound to the tenant. In every case, the result is a platform JWT (RS256) issued by auth-service and validated by Kong; downstream services never see the external provider's token.
1.1 Keycloak (base / default)
- OIDC Authorization Code Flow with PKCE from the portal;
auth-serviceexchanges the code at Keycloak's/tokenendpoint. - Keycloak holds the user's credentials (password, TOTP, WebAuthn where configured).
- Password policy enforced inside Keycloak (argon2id, zxcvbn ≥ 3, length ≥ 12).
- MFA: TOTP (RFC 6238) configured per realm; WebAuthn optional.
- Rate limit on
/v1/auth/loginat Kong: 5/min per IP + 10/min per email; Keycloak brute-force detector enabled as defence in depth.
1.2 Tenant external OIDC (brokered through Keycloak)
- Tenant registers their OIDC discovery URL via
admin-dashboard. auth-servicecreates a Keycloak "Identity Provider" (OIDC kind) and attribute mappers (email, sub, groups → platform roles) via Admin REST.- User signs in through Keycloak's broker URL → redirected to tenant IdP → returned with
id_token→ Keycloak applies mappers → Keycloak issues a broker-signedid_tokentoauth-service. auth-servicetreats the broker token'ssub+ provider claim as the external identity key, stores/links inexternal_identities, and issues a platform JWT.
1.3 Tenant external SAML 2.0 (brokered through Keycloak)
- Tenant uploads SAML IdP metadata XML via
admin-dashboard. auth-servicecreates a Keycloak "Identity Provider" (SAML kind) + attribute mappers.- SP-initiated and IdP-initiated SSO both supported;
<Response>signatures verified against the tenant's signing cert. - Signed + encrypted assertions mandatory for production. NameID format:
emailAddressunless the tenant overrides topersistent. - Signing/encryption keys per tenant stored in Vault (KMS-wrapped), rotated on tenant request.
1.4 Firebase (legacy / optional)
verifyIdTokenserver-side via Firebase Admin SDK; match claimemailto a provisioned user.- Enabled only on tenants flagged
legacy=trueintenant_identity_providers; no new tenants are onboarded onto this provider.
1.5 Native password (fallback only)
- Used exclusively for break-glass admin accounts and environments with no Keycloak (unit/integration tests). Argon2id m=64MB, t=3, p=4; zxcvbn ≥ 3 at set-time.
2. Platform JWT
- RS256, keys in Vault, rotated every 30 days (JWKS
kidrotation handled by Kong's ETag cache). - Access 15 min, refresh 30 d (rotating).
jtistored in Redis on revoke (logout); access-token blacklist TTL = remaining access-token lifetime.- Claims include
tenantId,accountId,userId,scopes[],roles[], andidp(e.g.keycloak,tenant-oidc:acme,tenant-saml:acme,firebase-legacy) so audit downstream can observe which provider authenticated the session.
3. API keys
- Raw key format:
ghasi_live_<24 random base62>; shown ONCE at creation. - Stored as
sha256(raw). - Rotatable; revocable; optional expiry.
- Scope-checked server-side (defence in depth — Kong only enforces authenticated).
- Issued and managed independently of the IdP provider; API keys are tenant-scoped, not user-scoped.
4. Authorization
- RBAC stored in
auth.user_roles; role → scope set. - External IdP groups/claims are mapped to platform roles via Keycloak IdP mappers (configured per tenant).
- Scopes embedded in platform JWT at issue; refreshed on each login/refresh.
- Authoritative check in each service's guards; Kong does NOT enforce scopes.
5. SCIM 2.0 (enterprise tenants)
auth-serviceexposes/scim/v2/Usersand/scim/v2/Groupsfor tenant IdPs that prefer push provisioning (Okta SCIM, Azure AD SCIM).- Bearer token per tenant, rotatable; scope restricted to that tenant's namespace.
- SCIM changes are mirrored into the tenant's Keycloak user store via Admin REST.
6. Audit
All auth state changes published as domain events with retention ≥ 30 days. Linked to traceparent for end-to-end trace. SSO-specific events include:
| Event | Trigger |
|---|---|
auth.idp.configured.v1 | Tenant registers an external OIDC/SAML IdP |
auth.idp.removed.v1 | Tenant deregisters |
auth.external_identity.linked.v1 | User's platform account is linked to an external subject |
auth.external_identity.unlinked.v1 | Link removed |
auth.sso.session.started.v1 | Successful external-IdP-mediated login |
auth.sso.session.failed.v1 | Federation failure (signature invalid, unknown issuer, revoked cert) |
auth.user.erased.v1 | GDPR erasure |
7. Secrets
| Secret | Store |
|---|---|
| Platform JWT RS256 private key | Vault Transit |
| Keycloak admin credentials (Admin REST) | Vault KV |
| Keycloak realm signing keys | Managed inside Keycloak; Vault-sealed backup exports |
| Per-tenant SAML SP signing / encryption keys | Vault KV (KMS-wrapped) |
| Per-tenant OIDC broker client secrets | Vault KV |
| SCIM bearer tokens (per tenant) | Vault KV |
| TOTP secret (per user, native fallback only) | KMS envelope in DB column |
| Firebase service account (legacy) | Vault |
| SMTP creds (if any) | Vault |
| DB password | Vault (dynamic) |
8. GDPR
Erasure (auth.user.erased.v1) hashes email + removes totp_secret, password_hash; retains userId for audit linkage. For externally federated users, auth-service also issues a Keycloak Admin REST call to delete the user from the realm; for brokered tenant IdPs the platform retains the pseudonymous external subject hash for audit reconciliation only.
9. Threat model — additions from multi-IdP
| Threat | Mitigation |
|---|---|
| Malicious tenant registers an IdP that impersonates another tenant's users | Tenant scoping on external_identities (tenantId, providerId, externalSubject); rejects cross-tenant linking |
| Tenant IdP signing key rotation not reflected on our side | Keycloak auto-refreshes OIDC JWKS / SAML metadata on schedule; auth.idp.keys.rotated.v1 emitted |
| Compromised tenant IdP → account takeover in our platform | Per-tenant blast radius; admin-dashboard supports emergency "disable IdP for tenant" (writes to tenant_identity_providers, fans out via auth.idp.disabled.v1) |
SAML <Response> replay / XSW attacks | Keycloak-enforced signature verification + assertion/response separation + InResponseTo binding |
| External IdP returns spoofed email for a privileged internal domain | Reserved-domain allowlist in auth-service; e.g. *@ghasi.io cannot be claimed by any tenant IdP |