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:
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
nameyou 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¶
- They connect with a token, and the middleware runs.
- If it lets them in, their game fetches the server's client files. Nobody downloads anything before your check passes.
playerJoinfires, withplayer.token,player.accountIdandplayer.metadataalready 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.