Skip to content

Signing in

Players can start the game with a token, which your gamemode checks as they connect. You can issue tokens from your website, a launcher or a Discord bot. Your check decides whether they join, and as whom:

  • their name, which can differ from the one they asked for;
  • an account id;
  • any metadata you like, such as an avatar or a rank.
server.auth = async ({ token, ip }) => {
  const user = await accounts.verify(token);     // your own service
  if (!user) return "Your sign-in has expired: sign in again on the website";
  if (user.banned) return false;
  return { name: user.username, accountId: user.id, metadata: { avatar: user.avatarUrl, rank: user.rank } };
};

server.on("playerJoin", (player) => {
  console.log(player.name, player.accountId, player.metadata.avatar);
});

Players start the game with their token:

sagl.exe play.example.com:7777 --token eyJhbGciOi...

A launcher can set the SAGL_TOKEN environment variable instead, which keeps the token off the command line.

Signing in is optional. With server.auth left unset, anyone can join under the name they choose. You can still sign players in your own way, such as with a login dialog in game, and use the same fields: set player.accountId and player.metadata yourself.


server.auth

server.auth = middleware;          // or the `auth` option
server.auth = null;                // off again: anyone can join
server.authTimeout = 15_000;       // ms it may take (the default)

While it's set, every player needs a token. Those without one are turned away with "Missing authentication data". Players already in game aren't affected when you turn it on or off.

The middleware gets an AuthRequest:

Field
token What they started the game with. Never empty.
name The name they asked for.
ip Their IP.
host, port The address they connected to, as they typed it.
clientVersion Their SAGL client, e.g. "sagl-client 0.2.0".

It can answer straight away or asynchronously (an async function, or a promise):

It returns
{ name?, accountId?, metadata? } They join. name replaces the one they asked for (3-24 letters, digits and _[]().$@=).
nothing They join as they are.
a string They're turned away, and see the string.
false They're turned away with "Sign-in failed".

They're also turned away with "Sign-in failed" if it throws or doesn't answer within authTimeout. The error is logged, so you can see what went wrong.

If someone signs in with an accountId that's already in game, the old session is kicked ("You signed in somewhere else"). That's usually the same person, back after a crash or on another computer.

The name they join with follows the usual rules:

  • A name you return must be 3-24 letters, digits and _[]().$@=. If it isn't, they're turned away ("The server couldn't give you a name"), and the name is logged.
  • If another player already has that name, they're turned away with "That name is already in use". If that player is on the same account, the old session was kicked first, so there's no clash.
  • If you return no name, the one they asked for has to pass the same checks.

In order

  1. They connect with a token, and the middleware runs.
  2. If it lets them in, their game fetches the server's client files. Nobody downloads anything before your check passes.
  3. playerJoin fires, with player.token, player.accountId and player.metadata already set.

Players turned away never reach playerJoin, and there's no event for them. Refusals go to the player. Errors and timeouts are logged on the server: auth: signing in <name> (<ip>) failed: <reason>. To keep your own record, such as counting failures by IP, do it in the middleware before returning.

TypeScript

The types are exported:

import type { AuthMiddleware, AuthRequest, AuthAccept } from "@sagl/server";

const auth: AuthMiddleware = async (req: AuthRequest): Promise<AuthAccept | string> => {
  const user = await accounts.verify(req.token);
  return user ? { name: user.username, accountId: user.id } : "Invalid token";
};
server.auth = auth;

On the player

Property
player.token What they started the game with, or null. With server.auth on, it's the token that signed them in. Without it, it's whatever they brought, for you to check if you like.
player.accountId server.auth's accountId, or yours to set: null until then.
player.metadata server.auth's metadata, and whatever you add: player.metadata.vip = true. It's kept on the server; send players what they should see.

player.data is still yours for gamemode state. metadata holds what signing in told you about them.