Funcy ID: five ways to sign in, one account, instant sign-out by Alikhan UrumovFuncy ID: five ways to sign in, one account, instant sign-out by Alikhan Urumov
Funcy ID: five ways to sign in, one account, instant sign-out
The identity provider for Funcy Network, a set of independent games that share one player account and one balance. Every game has its own site, backend and database; this service is the only thing they all trust about who a player is.
Players sign in with a password and optional TOTP, a one-time email code, Google, Telegram, or an EVM or Solana wallet. Every game verifies the same RS256 token locally against the published key set. Go, PostgreSQL and Redis; 14 migrations and 241 test functions.
The constraint
One player has to be one person everywhere — one balance, one history, one name in chat — while the games know nothing about each other. And nobody outside may be able to pass as somebody else.
One person, from verified facts only
A person is created only from something cryptographically proven: a provider's signature, a mailbox proven by code, or a wallet signature over our own one-time challenge. Nothing in a request body is trusted
Every service keys players by one internal id. Google, Telegram and wallet identifiers never become keys anywhere else
Accounts collapse only on a verified email, and the race between two first logins is settled by a unique index in the database, not by application code
Merging two accounts is treated as a money operation, not an UPDATE: bindings move in one transaction and a durable event leaves through the outbox, so the wallet, game history and chat each migrate their own state idempotently
Sessions that end when the player says so
15-minute access tokens, and refresh tokens that rotate on every use. Presenting an already-rotated token revokes the whole session
An incident showed that a signed-out player could keep playing until the access token expired. Now every revocation writes a deny key that each game's gateway checks on every request, and the identity service checks it on its own endpoints too. If that store is unreachable, reads fail open and anything that moves money fails closed
A short grace window stops two open tabs from looking like token theft and signing the player out of every game at once
Players see every live session with its device, country and game, and can end any of them from any other
Moving off Cognito without a mass password reset
Cognito never releases password hashes, so passwords migrate lazily: on a player's first sign-in the password is checked against Cognito once, then stored as our own argon2id hash. From then on AWS is not on the path
The proof is a completed authentication whose subject matches the stored account, not an HTTP 200. An account that answers with an MFA challenge is refused rather than silently stripped of its second factor
If Cognito is down the answer is "try later", never "wrong password", and response time has a floor so timing cannot reveal who was in the old pool
The user-pool import is idempotent and accounts for every row: imported, linked, skipped or rejected with a reason
Built to be operated
Liveness and readiness are separate probes, so a database blip cannot restart every pod at once
Sign-in codes go out through two independent mail routes, after a single empty mail setting once locked everyone out of an admin panel
Uploaded avatars are identified by their bytes rather than their headers, re-encoded, and stripped of metadata, GPS included
Like this project
Posted Sep 28, 2026
The identity provider behind Funcy Network: one person per verified email, RS256 tokens every game checks locally, and sign-out that takes effect at once.