Verifying Players
If your game has its own backend — saving progress server-side, running a shop, awarding anything of value — it must not take the player's identity from the game client. Verify it against Y8 instead.
The player object is display data, not proof of identity
Everything the SDK gives you about the signed-in player, the pid included,
lives in the browser and can be modified by whoever is playing. It is there
so you can greet someone by name and show their avatar.
A player who edits their pid before your game sends it to your server is
then, as far as your server knows, somebody else.
If your game has no backend of its own, there is nothing to do here — the SDK already authenticates every call it makes to Y8 on your behalf.
How it works
Three steps, and the important part is that step 3 happens on your server, not in the game:
- The player signs in and the SDK reports it.
- The game asks the SDK for the player's access token and sends that token — and nothing else — to your backend.
- Your backend asks Y8 who the token belongs to, and believes Y8's answer.
The token is the only thing that crosses. Your backend never receives a player id from the client, so there is nothing for a player to tamper with.
Step 1 and 2 — in the game
const accessToken = y8Sdk.getAccessToken();
if (accessToken) {
await fetch("https://your-game-backend.example/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ accessToken }),
});
}
getAccessToken() is synchronous and returns null when nobody is signed
in, so call it after authentication has been reported.
Send the access token, never the full token object
There is also a getToken() method, which returns the whole token
object including the refresh token. A refresh token can mint new
access tokens, so it must never leave the SDK — not to your own backend,
not anywhere. getAccessToken() returns only the single string your
server needs.
JsResponse<Y8Token> response = await Y8.Instance.GetTokenAsync();
if (response.IsSuccess)
{
string accessToken = response.Data.access_token;
// Send accessToken — and only that — to your backend.
}
IsSuccess is false when nobody is signed in, so call it after
authentication has been reported.
Send the access token, never the full token object
Y8Token is the whole token object including the refresh token. A
refresh token can mint new access tokens, so it must never leave the
game — not to your own backend, not anywhere. Send only
access_token.
Functions.getToken returns the whole token as a JSON string ("null"
when nobody is signed in). Load it into a JSON object, take
JSON.Get("access_token"), and send that value — and only that — to your
backend, for example with the AJAX object.
Send the access token, never the full token object
The JSON string from getToken includes the refresh token. A
refresh token can mint new access tokens, so it must never leave the
game — not to your own backend, not anywhere. Send only the
access_token value, never the string itself.
var token = Y8.getToken();
if (token != null)
{
var accessToken:String = token.access_token;
// Send accessToken — and only that — to your backend.
}
Y8.getToken() returns null when nobody is signed in, so call it after
authentication has been reported.
Send the access token, never the full token object
Y8.getToken() returns the whole token object including the refresh
token. A refresh token can mint new access tokens, so it must never
leave the game — not to your own backend, not anywhere. Send only
access_token.
Step 3 — on your server
Ask Y8 who the token was issued to:
GET https://account.y8.com/api/profile
Authorization: Bearer <access_token>
A 200 response describes the player the token actually belongs to:
{
"nickname": "…",
"level": 1,
"trust_details": { "email": "…" },
"pid": "…",
"locale": "en",
"avatars": {
"thumb_url": "…",
"thumb_secure_url": "…",
"medium_url": "…",
"medium_secure_url": "…",
"large_url": "…",
"large_secure_url": "…"
},
"version": "…",
"client_id": "…"
}
| Field | Type | Description |
|---|---|---|
pid |
string |
The player's id in your game; the same player has a different pid in every other game. The one to trust. |
client_id |
string |
The App ID the token was issued to. Compare it with yours. |
nickname |
string |
Display name. |
level |
number |
1 if the player has confirmed their email, otherwise 0. |
trust_details.email |
string or null |
When the player confirmed their email, or null if they have not. |
locale |
string |
The player's language setting, e.g. "en". |
avatars |
object |
Avatar URLs at 32, 50 and 160 px (thumb, medium, large). Each is null when the player has no approved picture; *_secure_url repeats *_url. |
version |
string |
A hash of the profile that changes whenever the player edits it. |
An app that Y8 has granted access to further profile fields, such as email,
also receives those.
Then, on your server:
- Use the
pidfrom this response. Ignore any identity the client sent alongside the token. - Check that
client_idequals your own App ID. Reject the login if it does not — see below. - On
401, reject the login. The token is invalid or expired. Have the game callrefreshToken()and retry once with the new token. - Verify on every sign-in, not once per account, then issue your own
session keyed to the verified
pid.
Why the client_id check matters
Access tokens are issued per application. Without step 2, a token obtained
from a different game could be replayed against your backend, and the
profile lookup would succeed — it is a perfectly valid token, just not one
issued to you. Comparing client_id to your own App ID is what makes the
check specific to your game.
What to store
Store the verified pid and key your own records to it. It stays the same for
a player in your game across sessions and devices, but it is specific to your
game: the same player has a different pid in every other game, so it cannot
be used to match players across games.
Do not store the access token as a long-lived credential. It expires, and once your own session exists you no longer need it — you have already established who the player is.