Olympus Docs
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:

  1. Require AAL2 step-up (delete is sensitive).
  2. Confirm prompt with typed username/email.
  3. Call your backend's DELETE /api/me.
  4. Backend calls Kratos's identity delete admin endpoint.
  5. 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.

On this page