Trust & attribution — the two-identity model¶
AI-anchored: the agent proposes, a named human approves, the write is attributably recorded, and the whole chain is passwordless. That's the invariant this chapter teaches — and this page is the surface intro. Two deep pages come next: attributed writes (SQL pattern) and, later, the identity-federation deep dives per cloud.
The one rule¶
The workload identity is who runs the code. The end-user identity is who asked for the write. Keep them separate. Every audit row carries both.
Never overload one identity to mean both. If your app's DB user is the end user, you've lost the auditable chain the moment the app connects on that user's behalf — and you've inherited a password-management problem. If you strip the end user before writing, you've lost accountability.
The two identities¶
┌────────────┐ edge auth ┌────────────┐ workload IAM ┌────────────┐
│ end user │─────────────────►│ your app │─────────────────►│ database │
│ (browser) │ signed JWT │ (pod / λ) │ passwordless │ │
└────────────┘ in a header └────────────┘ (mgd identity) └────────────┘
who asked │ │ │
for the write │ │ │
▼ ▼ ▼
immutable oid / workload role / connects as workload,
sub claim (never managed identity writes end-user oid
the display name) (rotated by cloud) into audit row
Per-cloud mapping (verified 2026-08-08)¶
| Layer | Azure | GCP | AWS |
|---|---|---|---|
| Edge / user auth | App Service or Container Apps Easy Auth — injects X-MS-CLIENT-PRINCIPAL (Base64 JSON) + X-MS-CLIENT-PRINCIPAL-ID (Entra oid) + X-MS-CLIENT-PRINCIPAL-NAME + X-MS-CLIENT-PRINCIPAL-IDP |
Identity-Aware Proxy (IAP) — injects x-goog-iap-jwt-assertion (signed JWT with sub + email). Always verify the JWT server-side; strip client-supplied x-goog-* headers. |
Application Load Balancer OIDC — injects x-amzn-oidc-identity (sub), x-amzn-oidc-accesstoken, x-amzn-oidc-data (signed JWT with sub + email). Verify the ES256 JWT via the ALB regional public-key endpoint. |
| End-user immutable id | Entra oid (never email — emails rotate) |
Google sub (numeric, never email) |
IdP sub from x-amzn-oidc-data |
| Workload identity | User-assigned or system-assigned Managed Identity on the App Service / Container App / AKS pod (Workload Identity) | Workload Identity Federation — GKE SA ↔ Google service-account binding; Cloud Run/Functions use their own runtime SA | IRSA on EKS, task role on App Runner / ECS, execution role on Lambda |
| Passwordless DB auth | Azure Postgres Flexible Server Entra ID authentication — access token audience https://ossrdbms-aad.database.windows.net/.default |
Cloud SQL IAM authentication — google-cloud-sql-connector exchanges the workload SA for a short-lived DB password |
RDS IAM authentication — generate_db_auth_token(...) mints a 15-min token using the workload role's AWS creds |
The deep page on each identity federation flow (managed-identity → Entra token → PG; workload-identity → IAM → Cloud SQL Connector; IRSA → sts:GetCallerIdentity → RDS token) is coming next in this arc.
Docs verified 2026-08-08¶
- Azure App Service — Work with user identities in AuthN/AuthZ: learn.microsoft.com/…/app-service/configure-authentication-user-identities
- GCP IAP — Signed headers: cloud.google.com/iap/docs/signed-headers-howto
- AWS ALB — Authenticate users through OIDC (
x-amzn-oidc-*headers): docs.aws.amazon.com/…/listener-authenticate-users - Azure Postgres — Entra ID auth: learn.microsoft.com/…/postgresql/flexible-server/how-to-configure-sign-in-azure-ad-authentication
- Cloud SQL — IAM database authentication: cloud.google.com/sql/docs/postgres/authentication
- RDS — IAM database authentication: docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
Why kept separate¶
- Least privilege on the DB side. The workload identity has narrow write rights; the end user has no DB rights at all. A compromised end-user session cannot escalate to arbitrary SQL.
- Rotation independence. Cloud-managed workload identities rotate on their own cadence. End users churn (join/leave, change emails, get renamed). Neither event touches the other's key.
- Attribution survives churn. The Entra
oid/ Googlesub/ IdPsubis a stable pointer; when a user leaves, the row still names who did the write even if the display name is gone. - AI in the loop. When the writer is an agent acting on a human's approval, both need to land in the row — see Attributed writes.
Never allow¶
- App connects as the end user. That means either you're storing user passwords or you're exchanging tokens on their behalf — both cost you the passwordless property and both couple DB access to user churn.
- Trusting a client-supplied
x-goog-authenticated-user-*/ raw JWT header without verification. IAP strips them at the edge; ALB signsx-amzn-oidc-data; App Service refuses to forward them. If your code reads them without verifying, you've re-opened the hole those layers closed. - Using
emailas the primary id. Email addresses rotate. Onlyoid/subis immutable.
Where the arc goes next¶
- Attributed writes — the SQL pattern that lands the end-user id in every row without giving them DB rights.
- Deep pages (coming) — one per cloud, walking managed-identity / workload-identity-federation / IRSA → passwordless DB IAM end to end, with Terraform.
Validate-only¶
No live edge-auth wiring in CI. The example service ships a stub verifier so the pattern is exercised without a real IdP.