Identity Management
Opt-in current-user identity availability, listing, addition, replacement, revocation, and primary-login selection.
An identity locates a stable user; it is not the user, a password credential, or necessarily a contact destination. IdentityManagement provides the current-user lifecycle for global email and global or tenant-scoped username/custom identities. It is opt-in: mount IdentityHttpApiGroupLive or bind IdentityHttpOperations only when your product exposes account-identity settings.
Security boundary
The built-in HTTP handlers read userId from the validated session. Mutation payloads never choose their owner. They pass that session to IdentityMutationPolicy, so the application can deny an operation or require fresh step-up based on operation, current identity, proposed identity, user, and session evidence.
IdentityMutationPolicyAllow is convenient but deliberately permissive. For account settings, provide a policy that requires the assurance and freshness appropriate to add, replace, revoke, and primary changes. Keep CSRF/origin controls and route exposure at the application boundary.
Replace, revoke, and primary selection require the updatedAt returned by list as expectedUpdatedAt. This is a compare-and-set token: stale tabs and concurrent requests fail rather than overwriting a newer identity state. The service also rejects revocation of the user's last login-eligible identity.
Service and HTTP surface
| Operation | Behavior |
|---|---|
availability | Normalizes a proposed identity and reports whether no active row currently owns it. This is advisory; atomic uniqueness decides races. |
list | Returns the user's active and historical identity rows as safe IdentityInfo values. |
add | Adds an identity after policy authorization. Email starts unverified; username/custom kinds are locally verified. |
replace | Atomically revokes the current row and inserts its replacement when expectedUpdatedAt still matches. |
revoke | Revokes the row when it is owned, active, current, and not the last login identity. |
setPrimary / client primary | Selects an active login-eligible identity as primary with CAS protection. |
Revoked and replaced normalized values are immediately reusable. If your product needs username quarantine or a cooldown, enforce it in application policy and storage rather than assuming a core tombstone reservation.
Browser client
The unified client exposes auth.identities; the standalone client is createIdentityClient from @effect-auth/core/Client.
import { createIdentityClient } from "@effect-auth/core/Client";
const identities = createIdentityClient();
const current = await identities.list();
await identities.replace({
identityId: current.identities[0].id,
expectedUpdatedAt: current.identities[0].updatedAt,
scope: { type: "global" },
kind: "username",
value: "new-name",
});Do not treat a successful availability response as a reservation. Submit the mutation and handle an identity conflict. After adding or replacing an email, start the email-verification flow for the returned identity; core does not mark a user-supplied email verified.
Direct service composition
Import IdentityManagement, IdentityManagementLive, IdentityManagementWithEmailAcceptanceLive, IdentityMutationPolicy, and makeIdentityManagementAudit from @effect-auth/core/IdentityManagement. Use IdentityManagementWithEmailAcceptanceLive when the provided EmailAcceptancePolicy must protect email add and replace operations. The audit wrapper emits safe identity lifecycle events after successful mutations. Direct service calls must still receive a trusted userId; they do not authenticate requests for you.
The maintained Effect-QB SQLite/D1 storage implements atomic replace, revoke, and primary transitions. Custom stores must preserve ownership, eligibility, last-login, uniqueness, and expectedUpdatedAt in the mutation transaction or predicate.