CookbookData & compliance
Self-service account deletion
Let users delete their own accounts
Many jurisdictions (GDPR, CCPA) require self-service account deletion. Olympus doesn't ship a built-in self-service delete; here's how to wire it.
Approach
Add a "Delete my account" button in your app's settings page. On click:
- Require AAL2 step-up (delete is sensitive).
- Confirm prompt with typed username/email.
- Call your backend's
DELETE /api/me. - Backend calls Kratos's identity delete admin endpoint.
- Triggers GDPR export side-effect (optional but recommended).
Implementation
Frontend
async function deleteMyAccount() {
// Step 1: ensure AAL2
const session = await fetch("/api/me").then(r => r.json());
if (session.aal !== "aal2") {
window.location.href = "/api/auth/stepup?return_to=" + window.location.pathname;
return;
}
// Step 2: confirm
const typed = prompt("Type your email to confirm:");
if (typed !== session.email) return;
// Step 3: delete
const res = await fetch("/api/me", { method: "DELETE" });
if (res.ok) window.location.href = "/account-deleted";
}Backend
export async function DELETE(request: Request) {
const session = await getSessionFromCookie(request);
if (!session) return Response.json({ error: "unauth" }, { status: 401 });
if (session.aal !== "aal2") return Response.json({ error: "aal2_required" }, { status: 403 });
// Optional: export their data first
const exportData = await gatherDsrExport(session.identity_id);
await emailToUser(session.email, exportData);
// Delete in Kratos
await fetch(
`${kratosAdmin}/admin/identities/${session.identity_id}`,
{ method: "DELETE" }
);
// Clean up your app-side data
await db`DELETE FROM user_data WHERE identity_id = ${session.identity_id}`;
// Anonymize audit log (don't fully delete, retain for compliance)
await db`UPDATE security_audit SET identity_id = NULL WHERE identity_id = ${session.identity_id}`;
// Clear cookies on response
return Response.json({ ok: true }, {
headers: { "Set-Cookie": "athena-session=; Max-Age=0" }
});
}What "deleted" means
- The Kratos identity row is gone, they can't log in anymore.
- Their sessions are revoked.
- Their OAuth2 consent grants are revoked (Hydra side).
- Your application-side user data should be deleted or anonymized.
- Audit log retains the fact of the deletion (with
identity_id = NULL).
Reversibility
Generally none. Hard delete is final.
If you want a soft-delete (with a grace period), instead of DELETE /admin/identities/<id>:
await fetch(`${kratosAdmin}/admin/identities/${id}`, {
method: "PATCH",
body: JSON.stringify([
{ op: "replace", path: "/state", value: "inactive" }
])
});State inactive prevents login but preserves data. Reverse with op: "replace", value: "active".
Compliance
GDPR Article 17 (right to erasure):
- User-initiated deletion is the foundation.
- 30-day SLA from request to completion is common; faster is better.
- Some data can be retained under legal grounds (tax records, fraud prevention), document those.
CCPA / California Privacy Rights:
- Similar, provide a self-service deletion path.
Related
- Cookbook, GDPR DSR export, companion runbook.
- Operate, Audit log retention, what to keep.
- Identity, Sessions, AAL, refresh