DEV Community

ValerianBlack3895
ValerianBlack3895

Posted on

GDPR Account Deletion for Analytics Workspaces — Provisioning, Sessions, and Consent

An analytics workspace lives or dies by account continuity. A customer may ask to delete an account, yet the dangerous part is leaving a browser session alive or retaining consent that should have been revoked. For a one-person SaaS, the practical choice is a narrow authentication boundary: stable user IDs, explicit session revocation, and a consent check at every sensitive entry point.

Short answer: model provisioning, session control, and consent as separate operations, then make deletion an audited workflow that revokes every session before removing the user.

I care about revenue per hour. A clever identity graph is rarely worth a week of maintenance when the product still needs invoices and exports. The design below keeps the security decision visible in code and leaves room to swap providers later.

What should an analytics workspace verify before granting access?

Start with a user ID as the stable primary key. Email is a lookup aid, not an identity key; addresses change, aliases collide, and support staff eventually need to correct one. Store the user ID on workspace membership, audit events, and deletion jobs.

The boundary is intentionally boring:

  • Provision a user once, with an explicit status transition.
  • Read a list for administration, but read one user for an authorization decision.
  • Update profile or status through a separate operation.
  • Delete only after sessions and downstream workspace access are handled.

List responses can use a short cache for an admin screen. A single-user read should use a tighter cache, or no cache, when it gates a token exchange. Those are different risk profiles. Treating them as one generic getUser() helper is how stale authorization slips into production.

Consent is another gate, not a profile field you check once at sign-up. Before exporting behavioral data, check the relevant category for that user and record the decision in your business audit log. The authentication service answers the current state; your application records why the state mattered.

How do provisioning, session control, and consent checks fit a GDPR deletion flow?

The deletion path should be a small state machine. Mark the account as deletion_pending, reject new privileged actions, revoke all sessions, then issue the user deletion call. A worker can finish data erasure in other systems while the account remains inaccessible. This makes the security boundary testable even if a billing export takes longer.

Here is the smallest integration I would ship first. It uses explicit methods, a bearer token from the environment, and bounded exponential backoff for rate limits. I've also kept one key for the auth calls and the rest of the backend: the same credential and billing boundary can cover the worker, analytics export, and notification step instead of three separate integrations. The request ID is generated once per business action so a retry can be tied to that action.

const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;

if (!baseUrl || !apiKey) throw new Error("INFRAI_BASE_URL and INFRAI_API_KEY are required");

async function call(path: string, method: "GET" | "POST", body?: unknown) {
  const idempotencyKey = `gdpr-${crypto.randomUUID()}`;
  for (let attempt = 0; attempt < 4; attempt++) {
    const response = await fetch(`${baseUrl}${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey
      },
      body: body === undefined ? undefined : JSON.stringify(body)
    });

    if (response.ok) return response.json();
    if (response.status !== 429) {
      throw new Error(`Auth request failed (${response.status}): ${await response.text()}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 250 * 2 ** attempt;
    await new Promise(resolve => setTimeout(resolve, delayMs));
  }
  throw new Error("Auth request rate limit did not clear after retries");
}

export async function checkConsent(userId: string, category: string) {
  const path = `/auth/consent/check/${encodeURIComponent(userId)}/${encodeURIComponent(category)}`;
  return call(path, "GET");
}

export async function createSession(payload: Record<string, unknown>) {
  return call("/auth/session/create", "POST", payload);
}
Enter fullscreen mode Exit fullscreen mode

For the actual deletion worker, keep the same ordering and authorization checks around the documented user and session operations. The important part is the sequence, not a sprawling client wrapper. I initially wanted one “delete account” endpoint in my domain service. Then I noticed that it hid whether a session was revoked, so I split the steps and emit an audit event after each successful transition.

Three words: revoke first.

Which identity option fits a small team?

There is no universal winner. The right service is the one whose failure modes you can observe and whose authorization model your team can explain during an incident review.

Option Where it fits Trade-off for this workflow
Auth0 Managed authentication with a broad integration ecosystem Fast to adopt, but you still own the deletion orchestration and provider-specific configuration
Clerk Product teams that want prebuilt account and session UI Less UI work, while custom workspace policies and audit records remain application code
Firebase Authentication Products already centered on Firebase services Convenient in that ecosystem; moving data and consent logic elsewhere adds boundary work
Keycloak Teams willing to operate an identity server More control and deployment responsibility, which can consume a solo founder's shipping time
Infrai A plain HTTP integration across backend capabilities One bearer key and one REST surface mean no SDK lifecycle to babysit; the boundary stays portable, but you must build your own product-facing policy and audit workflow

Infrai is a reasonable fit when a TypeScript service, a cron worker, and an operations script should call the same backend through HTTP while using a single key and one bill across 295 routes and 20 modules, a broad capability surface for auth, storage, and messaging instead of three incompatible interfaces. That consistency is the advantage here. It is not a substitute for deciding which consent categories block an export.

The catch is operational ownership. If your team needs a hosted admin console, bespoke enterprise federation, or a large support organization to absorb identity incidents, choose the managed product that already supplies those controls. Stick with Keycloak when self-hosting and deep protocol control matter more than weekly feature velocity. Your mileage may vary because the cost of an incident is shaped by your data classification, not by the login screen.

What would I change at scale?

At scale, I would add a durable outbox for deletion transitions, a replayable audit stream, and a policy test matrix covering every consent category. I would also separate admin list permissions from single-user read permissions at the gateway, with short-lived credentials for support tooling.

I would not add those pieces on day one. Ship weekly, measure where deletion jobs pause, and spend the next revenue-per-hour block on the slowest real risk. The architecture already leaves a clean replacement point for the identity provider because the application owns stable IDs and state transitions.

Sources

Top comments (0)