Authentication
Authentication

Two authorities, two credentials.

AMLService is a pure OIDC relying party — it stores no user accounts and no passwords. There are two kinds of caller, each with its own credential. The tenant is always derived from the credential, never sent in a request body.

CallerAuthenticates withCan do
Admin (you / the platform)OIDC bearer token (admin role, aud: aml-api)Onboard tenants, issue/revoke API keys, manage providers, risk profiles & quotas.
Tenant (your app / customer)API key (X-Api-Key)Screen subjects, bulk-screen, read usage, manage monitored subjects.

The dual-authority model

Screening & usage endpoints accept either an OIDC bearer token or a tenant API key — both resolve to a tenant and a role. Onboarding & key/quota management are admin-only (OIDC bearer, admin role). Roles map to policies:

PolicyAllowed rolesExample endpoints
Screening.Readcompliance_analyst, admin, auditorGET /v1/screenings/{id}, GET /v1/usage
Screening.Writecompliance_analyst, adminPOST /v1/screenings/check
AdminadminPOST /v1/tenants, quota & key management
Issuer base: https://aml-identity.dloizides.com — PROOViD's own OpenIddict IdP. The service is pluggable: switching IdPs is a config change (Identity__Authority + Audience), no code change.

Machine-to-machine — client_credentials

For automation: a service account exchanges its client secret for an admin token.

TOKEN=$(curl -s \
  -X POST https://aml-identity.dloizides.com/connect/token \
  -d grant_type=client_credentials \
  -d client_id=aml-admin-cli \
  --data-urlencode "client_secret=$AML_ADMIN_CLI_SECRET" \
  -d scope="aml-api roles" \
  | jq -r .access_token)

The access token is a signed JWT carrying the AML claims contract: sub, roles: ["admin"], tenant_id, aud: "aml-api". AMLService validates it via the IdP's JWKS (/.well-known/openid-configurationjwks_uri).

Human admin — hosted login (authorization_code + PKCE)

A person signs in through the IdP's hosted login UI instead of holding a client secret. The public PKCE client is aml-admin-console.

1. Redirect the browser to:
   https://aml-identity.dloizides.com/connect/authorize
     ?client_id=aml-admin-console
     &response_type=code
     &redirect_uri=<your-registered-redirect-uri>
     &scope=openid profile email roles aml-api offline_access
     &code_challenge=<S256(code_verifier)>
     &code_challenge_method=S256
2. The IdP shows its hosted login page; the admin authenticates.
3. The IdP redirects back with ?code=...
4. Exchange the code (POST /connect/token, grant_type=authorization_code,
   code, redirect_uri, client_id, code_verifier) for an access_token.
The bundled stats dashboard already implements this flow ("Sign in with PROOViD") — a working reference you can read.

Tenant API key — X-Api-Key

Tenants authenticate with the API key returned (once) at onboarding. Send it as the X-Api-Key header, or as a bearer token — both are accepted on tenant-scoped endpoints.

# Either header works on screening / usage endpoints
curl https://aml-screening.dloizides.com/v1/usage \
  -H "X-Api-Key: aml_live_7Qk2yP9sR4tU6vW8xZ1aB3cD5eF7gH9"

curl https://aml-screening.dloizides.com/v1/usage \
  -H "Authorization: Bearer aml_live_7Qk2yP9sR4tU6vW8xZ1aB3cD5eF7gH9"
Treat the key as a secret. It is shown once at onboarding and grants full tenant-scoped access. Rotate by issuing a new key (POST /v1/tenants/{id}/api-keys?label=ci) and revoking the old one (DELETE /v1/tenants/{id}/api-keys/{keyId}) — both admin-only.

Onboarding returns the credentials

A single admin call provisions everything. The apiKey and webhookSigningSecret are returned once — store them immediately.

curl -X POST https://aml-screening.dloizides.com/v1/tenants \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Compliance", "slug": "acme",
       "contactEmail": "ops@acme.example", "plan": "standard",
       "enabledLists": ["UN","EU","OFAC","UK"], "webhookUrl": "https://acme.example/aml-hooks" }'