Auth Service — Service Overview
Status: populated Owner: Platform Engineering + Security Last updated: 2026-04-19 Companion: DOMAIN_MODEL · API_CONTRACTS · SECURITY_MODEL · ADR-0001 Kong · 13 Security
Change log
- v1.2 (2026-04-19) — Rebaselined as a multi-provider identity service. Keycloak is the base/default IdP (also acting as OIDC/SAML broker).
auth-serviceexposes anIdentityProviderport with pluggable providers so that tenant organisations can federate their own corporate IdP (Azure AD, Okta, Google Workspace, ADFS, generic OIDC / SAML 2.0) for SSO. Firebase is retained only as a legacy/optional provider.
1. Purpose
auth-service is the canonical identity surface for Ghasi-SMS-Gateway. It owns user/account lifecycle, API keys, sessions, MFA, RBAC, and publishes JWKS consumed by Kong at the edge.
Crucially, auth-service is not itself a raw IdP. It owns an IdP Provider Abstraction — a pluggable set of concrete providers, all implementing the same internal port. This lets us:
- Ship a secure default out of the box (Keycloak) without third-party lock-in.
- Let enterprise tenants bring their own IdP for SSO (OIDC or SAML 2.0) without any downstream service caring.
- Run multiple providers in parallel (useful during migrations or for hybrid tenants).
Per ADR-0001, Kong performs edge authentication using:
- JWT plugin validating the platform JWT (issued by
auth-service, signed RS256) againstauth-service/.well-known/jwks.json. - Custom
ghasi-api-key-lookupplugin resolving API keys to consumer identities via a cached endpoint here.
Downstream services trust the platform JWT + X-Tenant-Id / X-Account-Id headers Kong injects. They do not see the external IdP token, and they do not know which IdP authenticated the user.
2. Bounded Context
Identity & Access (Generic / commodity). Clear separation from tenant-service (which would own org membership if added; currently IAM is consolidated here). auth-service acts as the bounded context's anti-corruption layer against external IdPs — all external provider claims are normalised into a platform-internal identity model before being persisted.
3. Responsibilities
| Area | What auth-service owns |
|---|---|
| Accounts / tenants | Tenant-scoped account records and their tenant_identity_providers binding |
| Users | Per-account user records (operator, customer, admin personas) |
| External identity links | external_identities table linking platform userId ↔ (providerId, externalSubject) |
| Credentials (native) | Password hashes (argon2id) — used only when a tenant is on the native provider; TOTP and WebAuthn (future) |
| Sessions | Platform access JWT issuance + rotating refresh tokens |
| API keys | Tenant-scoped key issuance, rotation, revocation, scope |
| JWKS | Public /.well-known/jwks.json consumed by Kong |
| RBAC roles | Role catalogue + assignment (enforced in services, not Kong) |
| IdP provider abstraction | Pluggable IdentityProvider port + concrete providers (Keycloak, Tenant-OIDC, Tenant-SAML, Firebase-legacy) |
| External IdP onboarding | Per-tenant OIDC discovery or SAML metadata registration, realm/IdP mapper provisioning in Keycloak via Admin REST |
| SCIM provisioning (enterprise) | Inbound SCIM 2.0 endpoint for tenant IdPs that push users/groups |
| Audit | auth.events.* for every security-relevant action, including SSO login, external IdP link/unlink, SCIM changes |
4. Non-Responsibilities
| Area | Owner |
|---|---|
| Being the raw OIDC OP / SAML IdP | Keycloak (the base provider) |
| Being an external tenant's own IdP | The tenant organisation |
| Rate limiting + JWT validation at the edge | Kong |
| Authorization at resource level (account scope checks) | Each owning service's guard |
| Session TLS termination | Kong + Cloudflare |
| SMS MFA delivery | Routed through sms-orchestrator like any other SMS |
| Secret storage | Vault (auth-service reads; does not store raw) |
5. IdP Provider Abstraction
5.1 Provider port
export interface IdentityProvider {
readonly id: string; // e.g. "keycloak", "tenant-oidc:acme", "tenant-saml:acme", "firebase-legacy"
readonly kind: 'oidc' | 'saml' | 'firebase';
verifyExternalToken(tokenOrAssertion: string): Promise<ExternalIdentityClaims>;
resolveExternalIdentity(claims: ExternalIdentityClaims): Promise<UserLinkResult>;
provisionUserFromClaims(claims: ExternalIdentityClaims, tenantId: string): Promise<User>;
revokeExternalSession?(externalSubject: string): Promise<void>;
}
auth-service dispatches to a provider based on the tenant's tenant_identity_providers binding and the request context (e.g. OIDC redirect from realm acme, SAML AssertionConsumerService callback).
5.2 Concrete providers
| Provider ID | Kind | Role | When selected |
|---|---|---|---|
keycloak | OIDC | Base / default IdP; also the broker for all tenant-federated IdPs | Every tenant by default; mandatory for any tenant using external SSO (Keycloak brokers the external IdP behind this provider) |
tenant-oidc:<tenantId> | OIDC | Tenant's own OIDC IdP (Azure AD, Okta, Google Workspace, generic OIDC) brokered via Keycloak | Tenant admin registered an OIDC discovery URL |
tenant-saml:<tenantId> | SAML 2.0 | Tenant's own SAML IdP (ADFS, Azure AD SAML, Okta SAML, generic SAML) brokered via Keycloak | Tenant admin uploaded SAML metadata |
firebase-legacy | Firebase | Retained for existing Firebase-based customers only | Tenant flagged as legacy; slated for retirement per migration plan |
5.3 How Keycloak is used
Keycloak runs as a managed deployment in the ghasi-identity namespace:
- One realm per environment (e.g.
ghasi-prod,ghasi-staging) for platform users. - Per-tenant IdP mappers (inside the same realm) for tenants who federate their own IdP — the tenant's IdP is configured as an "Identity Provider" in Keycloak, and users authenticate via the familiar
acme.ghasi.io/realms/ghasi-prod/broker/acme/...flow. This avoids multiplying realms. - Admin REST API is the only programmatic surface
auth-serviceuses against Keycloak (provisioning IdPs, mappers, clients). - OIDC endpoints (
/realms/.../.well-known/openid-configuration,/token,/userinfo,/logout) are used by the customer/admin portal during browser login flows.
See SECURITY_MODEL §1 for the per-provider auth flows.
5.4 Tenant onboarding for external SSO
| Step | Actor | Action |
|---|---|---|
| 1 | Tenant admin | In admin-dashboard → SSO Settings, chooses OIDC or SAML, supplies discovery URL / metadata XML |
| 2 | auth-service | Validates metadata, creates a Keycloak IdP mapper via Admin REST, writes the binding to tenant_identity_providers |
| 3 | auth-service | (Optional) Exposes a SCIM 2.0 endpoint for the tenant IdP to push users/groups |
| 4 | Tenant user | Signs in at app.ghasi.io, is redirected through Keycloak → tenant IdP → back → platform JWT issued |
6. Kong Integration (critical)
| Kong plugin | auth-service endpoint | Purpose |
|---|---|---|
jwt plugin | GET /.well-known/jwks.json | JWKS pulled with ETag + cache; rotated every 30 d — validates the platform JWT, not the external IdP token |
ghasi-api-key-lookup (custom) | GET /v1/api-keys/lookup?hash={hash} (internal) | Returns Kong consumer metadata for a hashed API key |
openid-connect (Kong Enterprise, optional) | Keycloak discovery URL | Only if we later let Kong itself validate OIDC tokens at the edge; not used today — validation stays inside auth-service |
7. Dependencies
| Dep | Purpose |
|---|---|
PostgreSQL (auth schema) | Users, accounts, api_keys, sessions, rbac_roles, tenant_identity_providers, external_identities, idp_session_audit |
| Keycloak | Base IdP + OIDC/SAML broker for tenant IdPs; own PostgreSQL schema keycloak |
| Redis | Session cache, JWKS cache, API key hash lookup cache, per-tenant IdP config cache |
openid-client, @node-saml/node-saml, keycloak-admin-client | OIDC + SAML client libraries |
firebase-admin | Legacy provider only |
| Vault | Signing key material (RS256 private key), Keycloak admin creds, per-tenant SAML signing keys |
| NATS JetStream | auth.events.* publication |
8. Flow — user login via Keycloak (default)
9. Flow — tenant external SSO (OIDC, brokered)
10. Flow — tenant external SSO (SAML 2.0, brokered)
Same as §9 with SAML-specific differences: Keycloak exposes the tenant's SAML SP endpoints; the tenant IdP posts a signed <Response> to Keycloak's ACS URL; Keycloak applies attribute mappers and then issues its own OIDC code to auth-service. From auth-service's perspective, the flow converges on the same provider=tenant-saml:<tenantId> code path.
11. Status
Foundation service; must be live before Kong + any service depending on JWT validation. Keycloak must be deployed and the default realm provisioned before auth-service starts. External IdP onboarding is a post-GA enterprise capability; the provider abstraction is in place from day one so that no downstream refactor is needed when the first external-SSO tenant lands.