Build and deploy
Player tokens
How your backend tells IRT who a player is, with JWTs signed by the project's signing keys, and how guests work without one.
A player token tells the router who a player is and what they may join. Your backend signs it after it has authenticated the player its own way; IRT never sees your users’ passwords or sessions.
Projects without a backend can let players join as guests instead.
The token
A player token is a JWT signed with HS256 using one of the project’s signing keys.
| Claim | Required | Meaning |
|---|---|---|
sub |
yes | The player ID you choose: 1 to 128 bytes, not starting with guest: |
prj |
yes | The project’s slug |
exp |
yes | Expiry, at most 24 hours after iat |
iat |
yes | When the token was issued |
room |
no | The only room this token may join |
meta |
no | JSON up to 1 KB, passed to the room’s onJoin |
The player ID is how the room, webhooks and voice know the player, and it stays the same across reconnects. A player has one connection per room: when the same player ID connects again, the older connection is closed.
Player IDs are public. Other players in the room see them as entity
owners, as voice identities and in anything the room sends. If your
backend knows players by email address or an internal account ID, give
each player an opaque ID for sub instead.
Use room when your backend decides who goes where, so a token for one
room can’t be used to join another. Use meta for data the room needs
on join and shouldn’t trust from the client, such as a display name or a
team.
Signing keys
Create signing keys in the dashboard, under the project’s keys, with
irt signing-keys create, or with POST /projects/{p}/signing-keys.
The secret is shown once. Store it on
your backend.
To rotate, create a second key, move your backend to it, then delete the first. Tokens signed with either key are accepted while both exist.
Signing a token
@irt/server signs tokens with WebCrypto, so it runs in Node, Deno, Bun
and edge workers:
import { playerToken } from "@irt/server";
export function tokenFor(player: { id: string; name: string }, room?: string): Promise<string> {
return playerToken({
key: process.env.IRT_SIGNING_KEY!,
project: "my-game",
player: player.id,
room,
meta: { name: player.name },
});
}
room and meta are optional. The token lasts 24 hours unless
lifetime sets fewer seconds; the client SDK reconnects with the same
token, so a short lifetime ends a long session at its next reconnect.
playerToken throws a TypeError for a player ID, meta or lifetime
the router would refuse. Any JWT library that does HS256 works too, with
the secret’s UTF-8 bytes as the key.
Hand the token to your client, which passes it to the client SDK when it connects. A token that is invalid or expired closes the connection with close code 4001.
Guests
When the project’s players.guests setting is on, a client can connect with
no token. The router then creates a guest player ID, starting with
guest:, and returns a guest token to the client. The client SDK keeps
it in the browser’s localStorage and sends it on every connect, so a
guest keeps the same player ID across reconnects, tabs and browser
restarts, and with it the entities it owns, its private state and its
voice identity. The token lasts 30 days and is renewed on each connect;
a guest away longer than that comes back as a new guest.
Guests suit games with no accounts. Turn players.guests off when every
player must come through your backend.
Rooms without a backend
Three more settings let clients manage rooms themselves:
players.createRooms: clients may create rooms and join them by a 6-character join code, and connecting to a room that doesn’t exist creates it.players.listPublic: clients may list active public rooms, with their player count andmeta. Hibernated rooms have no players and aren’t listed. Withplayers.guestsoff, listing needs a player token too, unlessplayers.listPublicWithoutTokenis on, which lets anyone list them.players.quickJoin, off by default: clients may join any public room with space whosemetamatches, or a new public room when none has space. See quickJoin. It’s for filling rooms with whoever comes; matchmaking by skill, parties and queues is your backend’s.
With players.createRooms off, only your backend creates rooms, through the
HTTP API. Games without a backend
shows how a client uses these settings.