Skip to content

Custom Token Sign In

automoto edited this page Aug 8, 2026 · 1 revision

Custom Token Sign-In

Custom tokens let a developer-run backend own player identity. The backend signs a short-lived JWT for a player, the game client exchanges it at POST /v1/auth/custom-token, and ggscale verifies the signature and mints a session. Use it when you already have accounts (a website, an existing game backend) and want ggscale to follow them.

Verification is public-key based. Your backend signs with a private key that never leaves your infrastructure, and ggscale stores only the public key. A ggscale database leak therefore exposes nothing that can mint player sessions.

Breaking change. Before 2026-08, custom tokens used a shared HS256 secret. That scheme is removed. Generate a keypair, switch your signer to EdDSA (or RS256), and save the public key in the control panel.

1. Generate a keypair

Ed25519 is recommended; it is small, fast, and misuse-resistant:

openssl genpkey -algorithm ed25519 -out custom-token-private.pem
openssl pkey -in custom-token-private.pem -pubout -out custom-token-public.pem

RSA (2048-bit minimum) also works if your JWT library lacks EdDSA support; sign with RS256.

2. Configure ggscale

In the control panel, open Account Tenant settingsCustom token sign-in and paste the contents of custom-token-public.pem. Saving an empty value disables custom-token sign-in. The private key stays on your backend.

3. Sign tokens on your backend

The token must carry external_id (your stable user id), a short exp, and the audience ggscale-custom-token:

token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
    "external_id": "user-42",
    "aud":         "ggscale-custom-token",
    "exp":         time.Now().Add(5 * time.Minute).Unix(),
})
signed, err := token.SignedString(ed25519PrivateKey)

Keep exp short, in minutes. The token is single-purpose: your backend hands it to the game client, and the client exchanges it immediately.

4. Exchange it in the client

curl -s -X POST http://localhost:8080/v1/auth/custom-token \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJhbGciOiJFZERTQSIs..."}'

The response is the standard session shape (access_token, refresh_token, player_id, expires_at). The first exchange for an external_id creates the player, and later exchanges return the same player.

Common errors

Status Meaning Fix
400 No public key configured for the Account Tenant. Save the key in the control panel.
401 Signature, algorithm, aud, or exp check failed. One opaque error by design; check key, algorithm (EdDSA/RS256), audience, and clock.
403 Player banned, or the Account Tenant hit its player limit. Not a token problem.

Key rotation

Save a new public key in the control panel and switch your backend's private key at the same time. Tokens signed with the old key stop verifying immediately; they live for minutes, so a brief deploy-window overlap is the only coordination needed.

Full reference: the /v1/auth/custom-token operation.

Clone this wiki locally