Skip to main content

Auth Service — Application Logic

Status: populated Owner: Platform Engineering + Security Last updated: 2026-04-19

Change log

  • v1.2 (2026-04-19) — Rebaselined to dispatch logins through the IdentityProvider port (Keycloak default + tenant OIDC/SAML brokered + Firebase legacy + native fallback). Added SSO start/callback, tenant IdP onboarding, and SCIM use cases.

1. Use Cases

Use caseEndpoint(s)Notes
RegisterUserUseCasePOST /v1/users (admin)Creates user in an existing account; binds to tenant's default provider
LoginUseCasePOST /v1/auth/loginNative / legacy only — delegates to nativeProvider or firebase-legacy
StartSsoUseCaseGET /v1/auth/sso/startResolves tenant's primary_provider_id; redirects to Keycloak broker URL
CompleteSsoUseCaseGET /v1/auth/sso/callbackExchanges OIDC code at Keycloak; runs IdentityProvider.verifyExternalToken + resolveExternalIdentity; issues platform JWT
HandleSamlAcsUseCasePOST /v1/auth/saml/{providerId}/acsVerifies SAML <Response> (via Keycloak broker → our ACS); issues platform JWT
HandleSamlSlsUseCasePOST /v1/auth/saml/{providerId}/slsSingle-logout propagation
RefreshTokenUseCasePOST /v1/auth/refreshRotates refresh token
LogoutUseCasePOST /v1/auth/logoutRevokes refresh; for SSO sessions, triggers Keycloak back-channel logout
IssueApiKeyUseCasePOST /v1/api-keysGenerates raw key, stores hash, returns raw ONCE
RevokeApiKeyUseCaseDELETE /v1/api-keys/{id}Soft-delete + publish event
LookupApiKeyUseCaseGET /v1/api-keys/lookup?hash=Internal only; Kong custom plugin caller
GetJwksUseCaseGET /.well-known/jwks.jsonCached 5m; includes all active + next keys
AssignRoleUseCasePOST /v1/users/{id}/rolesAdmin-only
RotateJwksUseCasecron + admin manualPromotes nextactive; publishes event
EnableTotpUseCasePOST /v1/users/me/mfa/totpReturns provisioning URI (native / Keycloak-managed only)
VerifyTotpUseCasePOST /v1/auth/mfa/totp/verifyDuring native login second factor
ConfigureTenantIdpUseCasePOST /v1/tenants/{tenantId}/identity-providersValidates OIDC discovery / SAML metadata; calls Keycloak Admin REST to provision broker IdP + mappers; writes tenant_identity_providers; emits auth.idp.configured.v1
DisableTenantIdpUseCasePATCH .../identity-providers/{id}Emergency disable; emits auth.idp.disabled.v1
RemoveTenantIdpUseCaseDELETE .../identity-providers/{id}Removes from Keycloak + our tables; rejected for default keycloak
TestTenantIdpUseCasePOST .../identity-providers/{id}/testRuns a synthetic auth probe
ScimProvisionUseCase/scim/v2/Users, /scim/v2/GroupsAccepts SCIM 2.0 CRUD; mirrors into Keycloak via Admin REST

2. Ports

PortAdapter
UserRepository, AccountRepository, ApiKeyRepository, SessionRepository, TenantIdpRepository, ExternalIdentityRepositoryPrisma
PasswordHasherargon2 module (native fallback only)
TokenSignerjose with Vault-fetched private key
JwksProviderIn-memory cache with TTL
IdentityProvider (base port)Dispatched by IdentityProviderRegistry to a concrete adapter: KeycloakProvider, TenantOidcProvider, TenantSamlProvider, FirebaseLegacyProvider, NativeProvider
KeycloakAdminClientkeycloak-admin-client — provisioning broker IdPs, mappers, realms
OidcClientopenid-client — OIDC discovery + token exchange
SamlClient@node-saml/node-saml — metadata + <Response> verification
ScimServerSCIM 2.0 HTTP handler
EventPublisherNATS JetStream

3. Token Lifecycle

TokenTTLClaims (key ones)
Access JWT15 minsub (userId), account_id, tenant_id, scopes[], roles[], kid
Refresh token30 d, rotatingOpaque string hashed in DB
API keyNo expiry unless setRaw-hash stored; scopes embedded via lookup

4. Key Rotation

  1. Admin (or cron) runs RotateJwksUseCase.
  2. Current next key promoted to active; old active moved to retiring (still in JWKS for 10 min window).
  3. Tokens signed with new active.kid.
  4. After 10 min, retiring key removed from JWKS.
  5. Event auth.jwks.rotated.v1 emitted; Kong's JWKS cache TTL ensures pickup within 5 min.

5. API Key Lookup (Kong integration)

Flow when a client calls Kong with X-Api-Key: ghasi_live_abc123...:

Kong (custom plugin) → GET /v1/api-keys/lookup?hash=sha256(key) → 200 { accountId, tenantId, scopes, status }
→ Kong caches for 30s
→ Kong injects X-Consumer-Id, X-Account-Id, X-Tenant-Id on upstream

If status != active → Kong returns 401. If lookup endpoint returns 404 → 401. On 5xx from this service → Kong fail-closed.