This is the part of a Steam multiplayer game that is most often done halfway. Steam can prove who a connecting player is, but only if the client and the server each do their part and each clean up after themselves. This page is the whole flow in order, the one rule per step, and the mistakes that break it.
Why bother #
A network library tells you a connection exists. It does not tell you whose it is. Without authentication a client can claim any Steam ID it likes. With it, the server asks Steam, and Steam answers whether the ticket really belongs to that account and that game.
The flow #
sequenceDiagram
participant C as Client
participant S as Server (Game Server)
participant ST as Steam
C->>ST: GetAuthTicket
ST-->>C: ticket (single use)
C->>S: ticket + claimed Steam ID (your netcode)
S->>ST: BeginSession(ticket, claimed ID)
ST-->>S: return value (OK or a rejection)
Note over S: Not OK: kick now, no callback will come
ST-->>S: validation callback (OK or a failure)
Note over S: OK: create the player. Not OK: end session and kick
Note over C,S: ... the match runs ...
S->>ST: EndSession(user)
C->>ST: CancelAuthTicket(ticket)
One rule per step #
| Step | Who | Call | The rule |
|---|---|---|---|
| 1 | Client | Client.GetAuthTicket |
Ask once per connection. Keep the ticket object, you need it again in step 7. |
| 2 | Client | your netcode | Send the ticket bytes and the claimed Steam ID to the server. |
| 3 | Server | Server.BeginSession |
Check the return value. It is an EBeginAuthSessionResult. Anything other than k_EBeginAuthSessionResultOK means Steam refused the ticket. Kick the connection. |
| 4 | Server | the callback you passed | This is the real answer. Only k_EAuthSessionResponseOK means the player is who they say. Do not create the player before it. |
| 5 | Server | the same callback, later | Steam calls it again if the player goes offline or the ticket is cancelled. Treat any non-OK answer as “this player is gone”: end the session and disconnect. Ignore a second OK. |
| 6 | Server | Server.EndSession |
Call it when the player leaves, including when they leave before step 4 finished and when validation fails. |
| 7 | Client | API.Authentication.CancelAuthTicket |
Cancel every ticket you were given when you stop playing. |
Valve’s own documentation states the two clean-up rules: the server must call EndAuthSession when the multiplayer session ends, and the client must call CancelAuthTicket for every handle it received. It also states that a ticket may be used only once.
What we measured #
These are results from running the calls against live Steam, not assumptions.
| Test | Result |
|---|---|
BeginSession with a bad ticket |
Returns InvalidTicket. The callback never fires. A server that waits for the callback waits forever. |
BeginSession twice with the same ticket |
The second returns DuplicateRequest. Tickets are single use. |
A bad BeginSession, then a good one for the same user (before the Toolkit fix) |
The good session’s answer was delivered to the bad one’s callback. The Toolkit now registers a session only when Steam returns OK. |
The common mistakes #
- Ignoring the return value of
BeginSession. The connection sits open and unauthenticated, never spawned and never kicked. - Spawning the player before the callback. The return value only says the ticket looked well formed. The callback says it is genuine.
- Trusting the claimed Steam ID afterwards. Take the identity from the validated session, not from later messages.
- Never calling
EndSession. Steam keeps tracking a player who has left, and a returning player’s new ticket can be refused. - Never cancelling client tickets. Every ticket is a live handle until you cancel it.
- Reusing a ticket. Ask for a new one for every connection.
- Handling only the first callback. The second one is how you learn a player went offline.
How the demos do it #
| Sample | Where |
|---|---|
| PurrNet, FishNet, NGO, Mirror | PlayerController. The client keeps its ticket and cancels it when the controller despawns. The server checks the BeginSession return value, ignores a second ticket from the same connection, kicks and ends the session on any failure, spawns once, and ends the session when the controller despawns. The pawn also ends the session when it is destroyed. |
| Netcode for Entities | SteamApprovalClientSystem and SteamApprovalServerSystem in SteamConnectionApproval.cs. The client system keeps and cancels its tickets. The server system turns a rejected BeginSession into a disconnect, and ends the session on every failure and disconnect. |
| Template | PlaceholderNetwork does not authenticate. Follow Integrating-Networking.md and the table above when you connect your own library. |
Checklist for your own game #
- Does the client keep its ticket and cancel it when it leaves?
- Does the server check the
BeginSessionreturn value? - Does the server wait for the callback before it creates the player?
- Does the callback handle a second, non-OK answer?
- Does every exit path call
EndSession: a failed validation, a disconnect before validation finished, and a normal leave? - Is the player’s identity taken from the validated session?