Authentication
When @colyseus/auth is installed alongside @colyseus/database and you pass database: to defineServer, the database becomes the user store and all auth routes mount automatically. The only callbacks left to write are the email-delivery ones.
What’s wired automatically:
- HTTP routes: every endpoint from
@colyseus/auth(register, login, forgot/reset password, OAuth callback) is spread into your router under the configured prefix. - User store:
db.auth.settingssupplies the user-store Backend API callbacks (onFindUserByEmail,onRegisterWithEmailAndPassword,onRegisterAnonymously,onResetPassword,onOAuthProviderCallback,onCheckBanned). Email delivery is not included: wireonForgotPassword(andonSendEmailConfirmation/onEmailConfirmedif you use email verification) yourself, or those flows silently no-op. - Ban gating at login:
onCheckBannedrejects banned users and surfaces{ reason, until }from the row. - JWT session revocation: JWTs carry a
tokenVersionclaim.db.auth.ban()bumps the version together with the ban fields, so every JWT issued before the ban is rejected on its next room join. HTTP routes verify only signature and expiry (see HTTP API Auth). Calldb.auth.bumpTokenVersion(userId)on its own for password changes, “sign out everywhere”, and forced rotation. - Password-hash handoff:
@colyseus/authhashes passwords before they reach the database; the DB never sees plaintext.
The same env secrets (JWT_SECRET, SESSION_SECRET) still apply. See Auth Module → Required Environment Secrets.
Minimal setup
Once database: is set on defineServer, the auth integration is live. No additional wiring is required:
import { defineServer } from "colyseus";
import { GameDatabase } from "@colyseus/database";
export const db = new GameDatabase({ connectionString: process.env.DATABASE_URL });
export default defineServer({
database: db, // ← wires @colyseus/auth routes + user store automatically
});Admin & moderation helpers
db.auth exposes account-management methods called from admin tooling or moderation flows:
await db.auth.ban(userId, { reason: "cheating", until: new Date(Date.now() + 86_400_000) });
const status = await db.auth.isBanned(userId); // { banned, reason?, until? }
await db.auth.unban(userId);
// Force-log-out: previously issued JWTs are rejected on the next room join
await db.auth.bumpTokenVersion(userId);Customizing callbacks
To wrap or replace individual callbacks, override keys on the auth.settings singleton after the server has started listening. listen() copies db.auth.settings into auth.settings, so an override applied earlier is overwritten. The example below adds an invite-code check during registration:
import { listen } from "@colyseus/tools";
import { auth } from "@colyseus/auth";
import app from "./app.config";
listen(app).then(() => {
// after listen(): wrap the database-provided callback
const original = auth.settings.onRegisterWithEmailAndPassword;
auth.settings.onRegisterWithEmailAndPassword = async (email, password, options) => {
if (options.inviteCode !== process.env.INVITE_CODE) {
throw new Error("invalid invite code");
}
return original(email, password, options);
};
});options is the object the client passes as the third argument of client.auth.registerWithEmailAndPassword(email, password, { inviteCode }) (the options field of the POST /auth/register body). A thrown error reaches the client as a 401 with the message.
See the Backend API catalog for the full list of hooks. Assign onto auth.settings, not db.auth.settings, whose getter returns a fresh throwaway object on every access.
Not using @colyseus/database? See Auth Module for the manual setup: implement the Backend API callbacks against your own database.