Skip to main content

Identity Service — API Contracts

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 05 API Design · API Path Conventions

Base URL: https://api.ghasi-ehealth.{tld} Internal base URL: http://identity-service:3000/internal/identity Auth: Bearer JWT issued by identity-service (RS256 from KMS-backed JWKS) except on anonymous endpoints marked anon. Error envelope: { error: ERROR_CODE, message, requestId, timestamp } — see ERROR_CODES.md.

1. Authentication (anon / auth-emitting)

MethodPathAuthSummaryScopes / Gates
POST/api/v1/auth/loginanonPassword login; returns {access, refresh} or {challengeId} for MFARate limit 5/min/IP
POST/api/v1/auth/mfa/verifychallenge-tokenComplete MFA during login
POST/api/v1/auth/refreshrefresh cookieRotate refresh + mint new accessOne-time-use
POST/api/v1/auth/logoutJWTRevoke current session
POST/api/v1/auth/logout-allJWTRevoke all sessions for caller
POST/api/v1/auth/password-reset/requestanonTrigger reset emailRate limit 3/h/email
POST/api/v1/auth/password-reset/confirmreset-tokenConfirm new passwordBreach-list check
POST/api/v1/auth/registeranonPublic self-registration (patient portal tenants only)Captcha
POST/api/v1/auth/verify-emailverify-tokenConfirm email, activates user
GET/.well-known/jwks.jsonanonJWKS for JWT verificationcached 1h
GET/.well-known/openid-configurationanonOIDC discovery document

Example request — POST /api/v1/auth/login

{ "email": "dr.ahmed@kch.af", "password": "...", "deviceFingerprint": "sha256:..." }

200 OK — no MFA

{
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"accessExpiresIn": 900,
"refreshExpiresIn": 28800,
"user": { "id": "usr_01H...", "tenantId": "ten_01H...", "email": "dr.ahmed@kch.af" }
}

401 MFA_CHALLENGE

{ "error": "IDENT_MFA_REQUIRED", "challengeId": "chl_01H...", "factors": ["totp","webauthn"] }

2. Federated identity

MethodPathAuthSummary
GET/api/v1/auth/oidc/:provider/startanonBegin OIDC Authorization Code + PKCE
GET/api/v1/auth/oidc/:provider/callbackanonOIDC callback; JIT link/provision
POST/api/v1/auth/saml/:provider/loginanonInitiate SAML redirect
POST/api/v1/auth/saml/:provider/acsSAMLAssertion Consumer Service endpoint
POST/api/v1/auth/saml/:provider/sloSAMLSingle Logout
POST/api/v1/auth/keycloak/token-exchangeupstream JWTRFC 8693 token exchange (broker mode)

Errors: IDENT_FEDERATION_UNAVAILABLE (503), IDENT_EXT_IDENTITY_MISMATCH (409), IDENT_ALLOWLIST_VIOLATION (403).

3. Self profile & access context

MethodPathAuthSummary
GET/api/v1/meJWTOwn profile
PATCH/api/v1/meJWTUpdate firstName, lastName, locale, timezone
GET/api/v1/me/access-contextJWTAggregated access context {roles, memberships, effectiveModules}
GET/api/v1/me/sessionsJWTActive sessions
DELETE/api/v1/me/sessions/:idJWTRevoke a specific session

4. User management (admin)

MethodPathScopeSummary
GET/api/v1/usersiam.users:readList users in tenant (paginated)
POST/api/v1/usersiam.users:createCreate user
GET/api/v1/users/:idiam.users:readGet user
PATCH/api/v1/users/:idiam.users:updateUpdate
POST/api/v1/users/:id/suspendiam.users:suspendSuspend
POST/api/v1/users/:id/reactivateiam.users:suspendReactivate
DELETE/api/v1/users/:idiam.users:deleteSoft-delete (deactivated)

Pagination: ?page=1&pageSize=50 (max 100). Filters: status, search, backend.

5. MFA & devices

MethodPathSummary
POST/api/v1/me/mfa/totp/enrollBegin TOTP enrollment, returns provisioning URI
POST/api/v1/me/mfa/totp/confirmVerify TOTP code, activates factor
POST/api/v1/me/mfa/webauthn/register/optionsCreate registration options
POST/api/v1/me/mfa/webauthn/registerFinalize WebAuthn registration
DELETE/api/v1/me/mfa/:factorIdRemove factor
GET/api/v1/me/devicesList own devices
POST/api/v1/me/devicesRegister new device
POST/api/v1/me/devices/:id/bind-offlineIssue offline binding cert
DELETE/api/v1/me/devices/:idRevoke device

6. API keys & service accounts

MethodPathScopeSummary
GET/api/v1/api-keysiam.apikeys:readList API keys (no secrets)
POST/api/v1/api-keysiam.apikeys:createCreate key (secret returned once)
POST/api/v1/api-keys/:id/rotateiam.apikeys:rotateRotate with overlap window
DELETE/api/v1/api-keys/:idiam.apikeys:revokeRevoke
GET/api/v1/service-accountsSUPER_ADMINList
POST/api/v1/service-accountsSUPER_ADMINCreate (client credentials)
POST/api/v1/service-accounts/:id/rotateSUPER_ADMINRotate secret
DELETE/api/v1/service-accounts/:idSUPER_ADMINRevoke

7. Licensing (module catalogue)

MethodPathScopeSummary
GET/api/v1/licensing/modulesJWTModule catalogue
POST/api/v1/licensing/modulesSUPER_ADMINCreate module
PATCH/api/v1/licensing/modules/:codeSUPER_ADMINUpdate description / dependencies

8. Licensing (assignments)

MethodPathScopeSummary
POST/api/v1/licensing/assignmentsSUPER_ADMINAssign license to node
GET/api/v1/licensing/nodes/:nodeId/assignmentsSUPER_ADMIN or TENANT_ADMINDirect assignments at node
GET/api/v1/licensing/nodes/:nodeId/effectiveJWTEffective set at node (inheritance walk)
PATCH/api/v1/licensing/assignments/:id/statusSUPER_ADMINChange status
PATCH/api/v1/licensing/assignments/:id/constraintsSUPER_ADMINUpdate seats/expiry
GET/api/v1/licensing/assignments/:id/historySUPER_ADMINChange history
POST/api/v1/licensing/bundles/:bundleCode/applySUPER_ADMINApply bundle (transactional)

Error responses: IDENT_LIC_DEPENDENCY_NOT_LICENSED 422, IDENT_LIC_MIN_NODE_TYPE 422, IDENT_LIC_ALWAYS_ON_IMMUTABLE 422, IDENT_LIC_TERMINATED 422, IDENT_LIC_DUPLICATE 409.

9. Internal (cluster-only, no JWT; ip-restricted)

MethodPathSummary
GET/internal/identity/users/:idLightweight user resolve
GET/internal/identity/users/by-external/:issuer/:subjectExternal identity lookup
GET/internal/identity/providers/:id/identityIdentity package consumed by tenant-service evaluate
GET/internal/identity/service-accounts/:clientIdActive-status probe
GET/internal/identity/licensing/effective?tenantId=&providerId=&nodeId= effective set
GET/internal/identity/licensing/nodes/:nodeIdDirect assignments (no walk)
POST/internal/identity/tokens/introspectOAuth 2.0 introspection for edge

10. Pagination & idempotency

  • Pagination: offset page/pageSize (max 100) or cursor cursor/limit (assignments history uses cursor).
  • Idempotency: Idempotency-Key header required on mutating /licensing/assignments and /users POSTs. 24h window. IDENT_IDEMPOTENCY_MISMATCH 409.
  • Request id: X-Request-Id echoed in response and all logs.
  • Correlation: Traceparent propagated per OpenTelemetry.

11. Rate limits (Kong)

SurfaceLimit
/api/v1/auth/* anon30/min/IP (login 5/min)
/api/v1/users POST60/min/tenant
/api/v1/licensing/* mutating60/min/tenant
/api/v1/me/*300/min/user

12. Error code catalogue (identity)

CodeHTTPMeaning
IDENT_USER_NOT_FOUND404User not in tenant
IDENT_EMAIL_DUPLICATE409Email exists in tenant
IDENT_USER_SUSPENDED422Action invalid for suspended user
IDENT_USER_DEACTIVATED422Action invalid for deactivated user
IDENT_MFA_REQUIRED401MFA challenge needed
IDENT_MFA_FAILED401Invalid TOTP/WebAuthn
IDENT_REFRESH_REPLAY401Refresh token reuse detected
IDENT_INVALID_CREDENTIALS401Password or OIDC assertion invalid
IDENT_CROSS_TENANT403Attempt to touch foreign tenant
IDENT_FEDERATION_UNAVAILABLE503Upstream IdP circuit open
IDENT_DEVICE_NOT_TRUSTED422Offline bind requires trusted device
IDENT_LIC_DEPENDENCY_NOT_LICENSED422License chain incomplete
IDENT_LIC_ALWAYS_ON_IMMUTABLE422Cannot terminate always-on module
IDENT_LIC_TERMINATED422Terminal state
IDENT_LIC_CROSS_TENANT403Node tenant mismatch
IDENT_IDEMPOTENCY_MISMATCH409Same key different body

See ERROR_CODES.md for shared platform errors.