A player's ready message is not the same as a decision that the match may start. Real-time communication distributes that decision quickly; it should not transfer authority to the client. The game-server flows in my archive provide context for discussing SignalR room membership and authoritative state.
The server assigns connections to groups
This independent ASP.NET Core fragment accepts only a room named demo. JoinLobby("demo") precedes Ready(true). The class still needs AddSignalR, MapHub, configured authentication and a client connection in an actual host.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
[Authorize]
public sealed class DemoHub:Hub
{
public async Task JoinLobby(string room)
{
if(room!="demo")throw new HubException("Unknown room");
Context.Items["room"]=room;
await Groups.AddToGroupAsync(Context.ConnectionId,room);
}
public async Task Ready(bool ready)
{
if(!Context.Items.TryGetValue("room",out var room))throw new HubException("Join first");
await Clients.Group((string)room!).SendAsync("PlayerReady",Context.ConnectionId,ready);
}
}
Authorize requires an authenticated caller. Context.Items holds connection-local room information, and AddToGroupAsync subscribes the connection to the group. Ready broadcasts PlayerReady; calling it before joining produces Join first. That is the extent of the sample: it does not implement a room store or match-start rule.
A SignalR group is not the player database
Groups route messages. They should not be treated as a durable membership list or the authoritative ready-state store. A connection ID is not a persistent player identity and may change after reconnection. A single player may also have several tabs or connections.
Resolve the player from the authenticated identity rather than trusting a submitted user ID. Room admission can then validate invitations, capacity and match state against the real store. A client should not be able to mark someone else ready by changing an argument.
Starting the match is an atomic transition
Two simultaneous ready requests can both observe that everyone is ready. Independently creating a match after each check can create two matches. The server needs a protected transition from waiting to starting to started; broadcasting MatchStarted does not provide that atomicity.
A versioned room snapshot or operation identifier can make the transition clearer. Events describe changes, while reconnecting clients need an authoritative answer to the current state. They should not be forced to reconstruct that state from an uncertain sequence of missed notifications.
Handle reconnects as a membership decision
Connection group membership is not durable player membership. Revalidate room access and rebuild the required subscriptions after reconnect rather than assuming every previous permission and state was restored. Whether a disconnected player's readiness is removed or preserved is also an explicit game rule.
In a multi-server deployment, a list inside one hub process is not shared room state. A shared store and SignalR scale-out messaging solve different problems. A working single-process demonstration does not establish distributed coordination.
The trust boundary still applies to messages
Validate method arguments, bound abusive message rates and avoid broadcasting unrelated private data. HubException can expose a safe intended error without including database or stack details. The client displays the authoritative decision; it does not grant itself a valid match by modifying local ready flags.
Exercise ready-before-join, an unknown room, simultaneous ready messages, disconnects and rejoining. The snippet demonstrates PlayerReady routing; a complete start flow additionally needs the state and idempotency contracts described above.