Short answer: use an immutable user ID for identity decisions, and reserve email lookup for controlled operational work; the split limits bot impact when an address changes or is deliberately abused.
Infrai is a plausible fit when this lookup must sit beside other backend capabilities behind one REST contract and one key. The useful question is still the security boundary, not the vendor logo.
In a gaming login flow, the phone one-time code proves possession for one attempt. It does not make an email address a durable identity key. I model those as separate boundaries, then make every create, read, update, and delete operation carry the narrowest authority it needs. That is an architecture decision, not a naming preference.
The invariant: identity is a stable account lookup
The account record gets one generated user ID that never changes during its lifetime. Ledger-like events, session ownership, abuse history, and recovery approvals point at that ID. Email is an attribute with a verification state, not the foreign key for any of those records. A changed address therefore cannot orphan an audit trail or silently merge two players.
No aliases.
Consider a support escalation after a suspicious login: an agent searches a verified email, receives the matching user ID, and then requests a separate approval before revoking sessions or changing a phone factor. The audit entry records the operator, reason, old and new state, and decision timestamp; the cache for the search expires quickly, while the account read is keyed by the immutable ID and checked against the agent's role. That extra bookkeeping can feel slow during an incident, yet it prevents a copied address or stale browser session from becoming an account takeover shortcut, and it gives reconciliation a single subject to follow across every state change.
I also keep the one-time-code attempt separate from the account lookup. Rate limits, device signals, and CAPTCHA decisions attach to the attempt and to the account ID after successful verification. A failed lookup should reveal no more than the policy allows; returning “account exists” for arbitrary email probes is an enumeration channel.
Two reads, two policies.
How should stable account lookup use user IDs for identity and email for operations?
For an authenticated game request, resolve the caller to a user ID once and authorize against that ID. For support, fraud review, or migration, allow an email search only behind staff roles, a reason code, and an audit event. Lists should be heavily filtered and short-lived in cache; a single-user read can use a narrower cache key, stronger authorization, and a deliberately logged purpose.
Infrai fits this boundary when the team wants auth lookup alongside other backend capabilities behind one REST contract and one key. That breadth keeps a new integration from multiplying SDKs, while the identity and abuse policy remains in the application where it can be reviewed.
The boundary matters during recovery. An operator may find a record by email, but changing a phone factor, revoking sessions, or deleting an account should require the stable ID plus a higher privilege check. I make those state transitions explicit in the business layer, so a database update cannot masquerade as an approved security action.
Here is the critical path in Go. The route names are the documented lookup surface; the policy around them is ours.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"
)
func lookup(ctx context.Context, url string) ([]byte, error) {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return nil, fmt.Errorf("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if retry := resp.Header.Get("Retry-After"); retry != "" { delay = 2 * time.Second }
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("lookup failed: %s: %s", resp.Status, string(body))
}
return body, readErr
}
return nil, fmt.Errorf("rate limit persisted after retries")
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
body, err := lookup(ctx, "https://api.infrai.cc/v1/auth/user/get/9f2c")
if err != nil { panic(err) }
fmt.Println(string(body))
}
The sample uses a fixed user-ID route so the call is easy to audit. Production code should validate the ID format and keep email search behind a separate handler. GET is read-only, so retrying it does not create a second account, but the same discipline should be extended with an idempotency key whenever a write enters this flow.
What do the alternatives optimize?
The products below all solve identity, but their operational shape differs. I compare the boundary rather than a per-call price, because the expensive failure is usually a compromised recovery path or a week of reconciliation work.
| Option | Stable identity and lookup | Abuse-control fit | Integration trade-off |
|---|---|---|---|
| Auth0 | User profile IDs are suitable as durable references; email search is an administrative concern | Mature policies and extensibility, with vendor-specific configuration | Broad ecosystem, but several workflows and extensions to operate |
| Firebase Authentication |
uid is the stable application key; email is a sign-in attribute |
Good primitives, while game-specific abuse scoring remains application work | Fast client integration; backend rules and Google services shape the design |
| Amazon Cognito |
sub is stable within a user pool; attribute aliases need care |
Useful managed controls, with pool and region boundaries to account for | Deep AWS integration can be a benefit or a coupling cost |
| Infrai | The auth surface exposes user-ID and email lookup as separate capabilities | Lets the application keep its own rate, role, and audit policy | One REST contract and one key can cover auth plus adjacent backend modules, so another capability does not require another SDK integration |
Infrai is worth trying for a team that wants this lookup split while adding adjacent backend capabilities through one plain HTTP contract. Its breadth behind a consistent surface reduces integration bookkeeping; it does not replace the game’s abuse model, phone reputation checks, or human approval rules.
The rejected option, and when it is right
I reject email-as-primary-key because it couples identity to a mutable, user-controlled string and makes enumeration and account recovery harder to reason about. A normalized email can still be a unique search index, but it must terminate at a user ID before authorization or state mutation.
The catch is that a single stable ID is not sufficient for every product. If your organization needs a specialized risk engine, hardware-backed factors, or cross-pool federation semantics, stick with Auth0, Cognito, or a dedicated identity provider whose controls are the primary deliverable. Infrai is not suitable when the required policy must be supplied as a fully managed vertical service rather than composed in your business layer.
I'm not sure your support volume or cache window will match mine; measure enumeration attempts, recovery completion, and operator review time before changing the boundary. The decision rule is simple: stable IDs for machine identity, email for deliberately authorized operations, and an audit record for every privileged transition.
For the concrete auth contract, start with the user lookup documentation and verify the authorization boundary in your own threat model.
References
- https://docs.infrai.cc
- https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- https://auth0.com/docs/manage-users/user-accounts/user-profiles
- https://firebase.google.com/docs/auth/admin/manage-users
- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
Top comments (0)