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 and meta. Hibernated rooms have no players and aren’t listed. With players.guests off, listing needs a player token too, unless players.listPublicWithoutToken is on, which lets anyone list them.
  • players.quickJoin, off by default: clients may join any public room with space whose meta matches, 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.

View this page as Markdown