Skip to main content

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-service exchanges the code at Keycloak's /token endpoint.
  • 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/login at 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-service creates 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-signed id_token to auth-service.
  • auth-service treats the broker token's sub + provider claim as the external identity key, stores/links in external_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-service creates 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: emailAddress unless the tenant overrides to persistent.
  • Signing/encryption keys per tenant stored in Vault (KMS-wrapped), rotated on tenant request.

1.4 Firebase (legacy / optional)

  • verifyIdToken server-side via Firebase Admin SDK; match claim email to a provisioned user.
  • Enabled only on tenants flagged legacy=true in tenant_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 kid rotation handled by Kong's ETag cache).
  • Access 15 min, refresh 30 d (rotating).
  • jti stored in Redis on revoke (logout); access-token blacklist TTL = remaining access-token lifetime.
  • Claims include tenantId, accountId, userId, scopes[], roles[], and idp (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-service exposes /scim/v2/Users and /scim/v2/Groups for 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:

EventTrigger
auth.idp.configured.v1Tenant registers an external OIDC/SAML IdP
auth.idp.removed.v1Tenant deregisters
auth.external_identity.linked.v1User's platform account is linked to an external subject
auth.external_identity.unlinked.v1Link removed
auth.sso.session.started.v1Successful external-IdP-mediated login
auth.sso.session.failed.v1Federation failure (signature invalid, unknown issuer, revoked cert)
auth.user.erased.v1GDPR erasure

7. Secrets

SecretStore
Platform JWT RS256 private keyVault Transit
Keycloak admin credentials (Admin REST)Vault KV
Keycloak realm signing keysManaged inside Keycloak; Vault-sealed backup exports
Per-tenant SAML SP signing / encryption keysVault KV (KMS-wrapped)
Per-tenant OIDC broker client secretsVault 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 passwordVault (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

ThreatMitigation
Malicious tenant registers an IdP that impersonates another tenant's usersTenant scoping on external_identities (tenantId, providerId, externalSubject); rejects cross-tenant linking
Tenant IdP signing key rotation not reflected on our sideKeycloak auto-refreshes OIDC JWKS / SAML metadata on schedule; auth.idp.keys.rotated.v1 emitted
Compromised tenant IdP → account takeover in our platformPer-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 attacksKeycloak-enforced signature verification + assertion/response separation + InResponseTo binding
External IdP returns spoofed email for a privileged internal domainReserved-domain allowlist in auth-service; e.g. *@ghasi.io cannot be claimed by any tenant IdP