Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Authentication overview

This page summarizes how a player signs in to Open Play: which methods exist, which rules they all share, and which page to go to for details. If you are wiring up player sign-in, start here.

Sign-in methods

MethodWhen?SDKDetails
GuestQuick start without an account; progress is kept on the same deviceTS: signInGuest · Unity: SignInGuestAsyncGuest sign-in
Email and passwordA permanent account; includes password reset and changeTS and Unity: sign-up, sign-in, upgrade, password operationsEmail and password
Google (OAuth)One-click web sign-in; if there is a verified email, it links automatically to the existing accountTS: startGoogleOAuth, completeGoogleOAuth · Unity: noneSign in with Google
Native platform proof (Apple, Game Center, Play Games)Native mobile sign-insNo SDK helper yet; HTTP endpoint onlySign in with Google › Native proofs
Magic linkA single-use link sent by emailNo SDK helper yet; the server endpoint existsEmail and password › Magic link

Which methods to offer players is the game’s choice; for Google to work, it must be configured in the environment (Game settings › Sign-in methods).

Topics after sign-in: Sessions and CSRF, Account linking, Server-side verification.

Which method should I choose?

  • If an account is not required, start with guest; as the player progresses, link them to email or Google with guest upgrade. The player ID (playerId) does not change.
  • If you want multiple devices, you need email/password or Google; a guest identity depends on the secret the device stores.
  • For native mobile sign-in (Apple, Game Center, Play Games), HTTP endpoints exist, but the SDKs have no helper.

Rules that apply to every call

The rules below apply to all endpoints under /v1/auth/*.

1. Environment header. Every request carries x-crx-env: <environment-id>. If it is missing or not a UUID, the response is 401.

2. Content type. Requests with a body are sent with Content-Type: application/json.

3. CSRF. Every request other than GET requires double-submit CSRF protection: the __Secure-crx.csrf cookie and a matching x-csrf-token header. On every successful use the token is returned and changes. You first get a token with GET /v1/auth/csrf. Details: Sessions and CSRF.

4. Origin check. For state-changing requests coming from a browser, the Origin domain must be bound to the environment. See Architecture and hosting.

5. Field names are snake_case. Request and response bodies use snake_case (display_name, bootstrap_token, old_password). Two exceptions: displayName in the GET /v1/auth/session response, and the body/response of POST /v1/auth/session/display-name (displayName). The SDKs hide this difference from you and present camelCase.

6. Error bodies. Errors are mostly plain text (permission denied, rate limited…). The only meaningful error with a JSON body is the 403 {"code":"account_banned","message":"account banned"} response for a banned account. Status codes: 400 invalid input, 401 environment or session missing/invalid, 403 denied (wrong password, CSRF, expired token — the distinction is deliberately not leaked), 409 conflict, 422 unprocessable, 429 rate limit. Details: Errors.

Successful sign-in response

The sign-in, sign-up, refresh, and OAuth callback endpoints return the same envelope and write two cookies (__Secure-crx.session, __Secure-crx.refresh; HttpOnly; Secure; SameSite=Lax; Path=/, Max-Age=15552000 = 180-day upper bound):

{
  "player": {
    "id": "0192…",
    "display_name": "Guest",
    "email": null,
    "email_verified": false,
    "is_guest": true,
    "image": null,
    "created_at": "2026-11-03T10:00:00+00:00"
  },
  "tokens": {
    "session_token": "<64 hex>",
    "refresh_token": "<64 hex>",
    "access_token": "<jwt>",
    "expires_in_secs": 900
  },
  "is_new_player": true
}
FieldMeaning
player.idThe permanent player ID (UUID). Does not change on guest upgrade
player.is_guestWhether the account is still a guest
tokens.session_tokenAn opaque session token. Can also be used with Authorization: Bearer instead of the cookie
tokens.refresh_tokenOnly for /v1/auth/refresh and /v1/auth/logout
tokens.access_tokenA short-lived (15 min) EdDSA JWT; it is for offline verification by your game server. See Server-side verification
tokens.expires_in_secsThe lifetime of the access JWT (seconds)
is_new_playertrue if the call created a new player

Warning

In browser games, do not store the session and refresh tokens in JavaScript; the cookies are HttpOnly, and that is what they are for. The SDKs do not put token bodies in state, logs, or errors.

Rate limits

The auth endpoints are subject to fixed-window rate limits. When exceeded, 429 and the plain text rate limited are returned; there is no Retry-After header: you must wait out the window yourself. Do not resend a failed attempt immediately.

LimitKeyValue
Opening a guest (including re-sign-in)IP30 / 60 s
Email sign-inIP5 / 60 s
Failed email sign-in lockaddress + IP10 / 15 min (while locked, even the correct password gets 429; a successful sign-in refunds the allowance)
Sign-upIP · address10 / 60 s · 5 / 15 min
Guest upgradeIP · guest player · address10 / 60 s · 30 / 60 s · 5 / 15 min
Password changeIP · account10 / 60 s · 6 / 60 s
Password reset request (forgot) and magic linkIP · address5 / 60 s · 5 / 15 min
Password reset (reset)IP5 / 60 s (shared bucket with forgot and magic link)
OAuth startIP30 / 60 s
OAuth callbackIP60 / 60 s
Display name changeplayer10 / hour

If there is an extra proxy in front of the server and the trusted proxy addresses have not been declared, all players fall into a single IP bucket. See Architecture and hosting.

Endpoint summary

Method and pathSessionDescriptionPage
GET /v1/auth/csrfnot requiredGenerates a CSRF tokenSessions and CSRF
POST /v1/auth/sign-in/guestnot requiredGuest sign-inGuest
POST /v1/auth/sign-up/emailnot requiredSign-upEmail
POST /v1/auth/sign-in/emailnot requiredSign-inEmail
POST /v1/auth/guest/upgrade/emailguest sessionUpgrades a guest to emailEmail
POST /v1/auth/change-passwordrequiredChanges the password; all sessions closeEmail
POST /v1/auth/forgot-passwordnot requiredRequests a reset emailEmail
POST /v1/auth/reset-passwordnot requiredNew password with a tokenEmail
POST /v1/auth/sign-in/magic-link · GET /v1/auth/magic-link/verifynot requiredMagic linkEmail
GET /v1/auth/oauth/providersnot requiredProviders enabled in the environmentOAuth
POST /v1/auth/sign-in/oauth/{provider}not requiredStarts OAuth ({url, state})OAuth
GET /v1/auth/callback/{provider} · POST (form)not required, or required for linkCompletes OAuthOAuth
POST /v1/auth/native/{provider}not requiredNative platform proofOAuth › Native
POST /v1/auth/link/{provider}requiredStarts linking an identityAccount linking
POST /v1/auth/unlink/{provider}requiredUnlinks an identityAccount linking
GET /v1/auth/identitiesrequiredLinked identitiesAccount linking
GET /v1/auth/sessionrequiredSession and profileSessions
POST /v1/auth/session/display-namerequiredChanges the display nameSessions
POST /v1/auth/account/deleterequiredDeletes the player’s own account (irreversible)Account deletion
POST /v1/auth/refreshrefresh tokenRefreshes the session (rotation)Sessions
POST /v1/auth/logoutrefresh tokenCloses the sessionSessions
GET /v1/auth/jwksnot requiredJWT verification keysServer-side verification

The /v1/auth/organization/* endpoints (player organizations) are being deprecated: they work, but every response carries a Deprecation header and they are not described in this document. The /v1/auth/admin/* endpoints are for player administration with a server key (list, ban, rename, session revocation) and are listed in the endpoint reference.

Banned accounts

When an operator bans a player, all of the player’s sessions are revoked and their open realtime connection closes. How the player appears at sign-in:

  • A banned email account with the correct password: 403 and {"code":"account_banned","message":"account banned"}. A wrong password or an unknown address is an indistinguishable plain-text 403; the ban status does not leak.
  • A banned guest: if a guest sign-in is made with the same bootstrap secret, or by presenting the banned player’s old session, 403 account_banned is returned again and no new player is created.
  • GET /v1/auth/session returns 401 for a banned account (the ban code does not leak); refresh, the OAuth callback, and guest upgrade are rejected indistinguishably for a banned owner.
  • The response contains no ban reason or end time.
  • SDK behavior: in TypeScript the session state becomes banned; in Unity SessionStatus.Banned. Neither opens a new guest, and neither deletes the stored guest secret (so a banned player cannot escape by opening a new guest). Show the player that the account is suspended; do not build a retry loop.