Soundmind
Unified Login Server
Multiple services share a single account. Parent phones and child devices live under opposite network conditions, so the session policy itself had to be split in two.
- Role
- Backend · Operations dashboard
- Period
- 2025.07 ~ Present
- Stack
- Spring BootJava 17MariaDBRedisNext.js
Outcome
Instant revocation
All account tokens revoked when token reuse is detected
2 types
Token policies separated by user type
01The problem
Parents connect frequently from regular smartphones, so the standard approach of continuously refreshing short-lived tokens fits them. Child devices, however, keep their network mostly closed under management policy, so refresh requests themselves fail often. Using the same policy logs child devices out constantly, but handing everyone long-lived tokens amplifies the damage if one is stolen.
02What I decided
I split the policy by user type. Parents use short-lived tokens with rotating refresh tokens; if a refresh token is reused, I treat it as theft, immediately invalidate the account's entire token lineage, and record the event. Child devices keep a long-lived single session with no refresh, with a cap on the number of sessions. Cross-service auth events pass through retries and a holding store so an admin can recover them.
- Parents: short-lived tokens + rotating refresh, full lineage revocation and audit logging on reuse detection
- Child devices: long-lived single session, capped concurrent session count
- Cross-service notifications retry with exponential backoff, then archive failures for manual reprocessing
- Built scripts that build and deploy only the changed services, plus a monitoring stack
03From one login to an account platform
It began as a login server, but as in-house services joined one by one the requirements grew into an account platform. Patching code for every new service would not hold, so joining, linking, propagation, and cleanup each became structure.
one account, many products
Products join by registration, events fan out as webhooks, and failures are parked, never lost.
Joining is registration
A new product joins by registration, not by a code change: a client identifier, API key, allowed IPs, and redirect URLs go into the database, and the CORS allowlist is generated from that same registration. Registration is the policy
The family link ledger
Guardian-child link requests, approvals, and removals live in one ledger inside SSO. A link made in any product is the same relationship everywhere, with caps on both sides
Propagating auth events
Links and withdrawals fan out to each service as webhooks, carrying idempotency keys, retried three times with exponential backoff, and parked for manual replay if all retries fail. Events never vanish quietly
Account lifecycle
Dormancy, deletion notices and deletion, and identity-verification retention windows all run on scheduled batches. Personal-data cleanup hangs on a schedule, not on an operator's memory
Operations dashboard
Member lookup, forced session termination, webhook replay, and audit logs live in a dedicated dashboard. Incident response ends on a screen, not in a database console
04Outcome
Child devices no longer get logged out by network conditions, and token theft scenarios are blocked at the account level the moment reuse is detected. The operations dashboard handles everything from session listing to forced termination.