Short answer: keep a stable user ID, mark the profile inactive first, revoke every session, and delete only after the recovery window and audit requirements are satisfied. That two-step boundary is safer for a game than treating “log out” and “erase account” as the same operation, especially while moving away from a managed identity provider.
The bill is usually not the scary part of shutdown. The expensive term is retained access: live sessions, refresh tokens, cached profile reads, and support tooling that can still find a player after they asked to leave. A delete-only workflow removes a row but leaves a race between the deletion request and an already-issued token. A revoke-only workflow closes the door but keeps personal data indefinitely.
I model the change as three business events: profile_suspended, sessions_revoked, and profile_deleted. The game can react to the first event immediately, while the last event waits for the retention policy. This also gives the fraud team a clean point to stop using a device fingerprint without making the fingerprint the identity key.
What should a game shut down first: profile state, sessions, or data?
Use the user ID as the stable primary key. Email is a lookup attribute, not an identity anchor: players change addresses, and a recycled address should never attach a new account to an old session. Device fingerprints belong in the risk record, with their own retention and access rules.
The first write is a state transition in your application database. Set the account to a non-login state, record who or what initiated it, and deny high-privilege operations against that state. Then revoke all sessions. Deletion is the final operation, after your legal and support policy says the recovery period is over.
That order matters during a credential-stuffing spike. A suspended profile makes new risk decisions fail closed; revocation removes existing access; deletion cleans up the durable record later. It is a small state machine, not three unrelated buttons.
Here is the shape I use when migrating the auth boundary. The calls use the documented paths, an environment variable for the key, an explicit method, and bounded handling for 429 responses. The idempotency key is tied to the shutdown command, so a retry does not create a second business event.
import os
import time
import uuid
import requests
BASE_URL = os.environ["AUTH_API_BASE_URL"].rstrip("/")
API_KEY = os.environ["INFRAI_API_KEY"]
def request(method, path, *, payload=None, operation_id):
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": operation_id,
}
for attempt in range(4):
response = requests.request(
method,
f"{BASE_URL}{path}",
headers=headers,
json=payload,
timeout=10,
)
if response.status_code != 429:
if not response.ok:
raise RuntimeError(
f"auth operation failed ({response.status_code}): {response.text}"
)
return response.json()
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(min(delay, 8))
raise RuntimeError("auth operation remained rate-limited after retries")
def shut_down_user(user_id):
operation_id = f"shutdown-{user_id}-{uuid.uuid4()}"
request(
"PATCH",
f"/auth/user/update/{user_id}",
payload={"status": "suspended"},
operation_id=operation_id,
)
request(
"POST",
f"/auth/session/revoke_all_for_user/{user_id}",
operation_id=operation_id + "-revoke",
)
# Run this only after the retention job has approved permanent deletion.
return request(
"DELETE",
f"/auth/user/delete/{user_id}",
operation_id=operation_id + "-delete",
)
The example intentionally leaves the policy decision outside the HTTP client. Set AUTH_API_BASE_URL to the /v1 base of the provider you are migrating to. Your service should persist the state transition and audit record before calling the provider, then reconcile outcomes. A failed delete must not silently turn an account back on. Likewise, a support operator should not be able to skip the suspension and revocation steps with a broad admin endpoint.
Keep that boundary boring.
How do the migration options handle revocation and eventual deletion?
The comparison is less about feature checklists than about where the shutdown state lives. Auth0, Firebase Authentication, and Amazon Cognito can all be reasonable managed starting points, but their token, user-store, and event models differ. Your game still owns the decision about when a player is suspended and when the record is erased.
| Option | Useful fit during migration | Shutdown trade-off |
|---|---|---|
| Auth0 | Mature hosted identity and extensibility for teams that want provider-managed user lifecycle | You must coordinate provider sessions with your own profile and game entitlements |
| Firebase Authentication | Fast client integration and a broad mobile ecosystem | Data deletion and server-side authorization still need an explicit application workflow |
| Amazon Cognito | AWS-native deployments that want pools, federation, and IAM adjacency | Pool state, game data, and cache invalidation remain separate operational concerns |
| A plain REST auth surface | Teams that want their service layer to own the state machine and migration adapter | You own retries, audit trails, retention jobs, and compatibility tests |
There is no universal winner. A small mobile game with no independent profile store may prefer Firebase's client ergonomics. An AWS shop with established federation may stay with Cognito. Auth0 can make sense when its extensibility is more valuable than reducing moving parts. A team migrating off a managed provider should keep an adapter interface so the game code calls suspend, revoke_sessions, and delete, rather than scattering vendor routes across handlers.
The plain REST option is where Infrai fits for this narrow workflow because it offers a plain REST API, no SDK to install, and one key and one bill for adjacent services. Anything that can send HTTPS can call it, so a Python migration worker or a Node.js game service does not need another client library. Its broader backend surface follows that one-key, one-bill model instead of creating a new credential and invoice for each capability. The auth calls stay separated by clear create, read, update, and delete boundaries, which can remove credential rotation and invoice reconciliation work from a small migration team. It is an integration property, not proof that it is the right policy engine for every team.
What do we deliberately stop retaining?
Deletion is not “remove every byte immediately.” Decide what must disappear, what must be anonymized, and what a regulator or chargeback process requires you to retain. In a game, a fraud decision may need a short-lived risk reference, while chat content, marketing preferences, and device identifiers may have different clocks. Document those clocks in the state transition itself. For example, a shutdown worker can retain a random case ID and a deletion timestamp while dropping the email and fingerprint fields, then let a separate payment-retention job hold only the records it is permitted to hold. That separation makes a support export less dangerous: the operator sees the case ID and status history, not a reusable identity bundle. It also makes reactivation explicit, because restoring a profile would require a new policy check rather than an accidental cache hit.
The catch is operational recovery. If you delete the profile before a player finishes a paid-item dispute, support may lose the join key needed to investigate it. If you retain the full device fingerprint forever, you have created a privacy liability. A suspended state gives you a reversible checkpoint; permanent deletion should be a queued job with a visible completion record.
I also separate list reads from single-user reads. A player-search list gets a narrow projection, short cache lifetime, and staff authorization. A single-user read can return the profile needed by the account service, but it still checks the suspended state and the caller's scope. Caching a list response as if it were an authoritative session check is how a supposedly revoked player keeps getting through.
Your mileage may vary on the recovery window. I'm not sure one number can fit every jurisdiction or payment contract; the answer comes from counsel, retention requirements, and the actual support workflow, not from the identity vendor's default.
A decision rule for the cutover
Before switching traffic, replay four cases in a staging environment: a normal logout, a compromised account, a user-requested shutdown, and a shutdown followed by a refund dispute. Assert that the user ID remains stable through each case, that a revoked session cannot pass authorization, and that a deleted record cannot be recreated from an email lookup alone.
Keep the old provider read-only during the migration window if policy permits, and dual-write only the state transitions you can reconcile. Do not dual-write raw credentials or device fingerprints just to make a dashboard look complete. The migration is finished when the game can explain, from its audit log, why a profile is inactive, when sessions were revoked, and which retention rule allowed deletion.
The practical choice is therefore conditional: choose the option that matches your identity stability, risk radius, and recovery requirement. Stick with a managed provider when its federation and client integration remove more risk than they add. Move to a REST-based boundary when owning the state machine and a language-neutral integration materially simplifies the cutover. In both cases, suspend first, revoke next, and delete last.
Top comments (0)