Tutorials - Authentication
By Gramps and Oilyraincloud
In this tutorial we will talk about authenticating users with Steam. Networking is out of scope for this tutorial; you will need to implement your own networking code. You can check out the lobbies and various networking tutorials for more on that, or even use Godot's high-level networking. You can read more about the whole authentication process in Steam's documentation page on the subject.
Relevant GodotSteam classes and functions
Note
You may want to double-check our Initialization tutorial to set up initialization and callbacks functionality if you haven't done so already.
Setting Up Signals
First, in both your clients and server, you'll want to set two variables: connected_clients and pending_clients. These will both be a dictionary keyed by the peer_id (i.e. Steam ID) of each client to store their auth ticket and the unique auth ticket the local client used to authenticate with them. Auth tickets can only be used once, so you need to setup a new auth ticket per connected client and store it in this dictionary, as per Valve's documentation. More on that later.
# Set up some variables
var connected_clients: Dictionary[int, Dictionary] = {} # Dictionary containing information from other connected clients
var pending_clients: Dictionary[int, Dictionary] = {} # Dictionary containing information from pending clients
The format of the connected_clients dictionary will look something like this:
var connected_clients: Dictionary[int, Dictionary] = {
<int peer id>, {
"client_ticket": <PackedByteArray client ticket>, # you can also store the client ticket as a Dictionary, but you only need the bytes
"my_ticket": <Dictionary my ticket> # you need to store your ticket as a Dictionary so you can access the ID later
}
}
If you are using Multiplayer Peer networking for authentication, it's likely more useful to key this dictionary off the connected peer's Multiplayer Peer ID rather than their Steam ID. If so, you should add the Steam ID to the dictionary as such:
# When using Multiplayer Peer
var connected_clients: Dictionary[int, Dictionary] = {
<int peer id>, {
"client_ticket": <PackedByteArray client ticket>,
"my_ticket": <Dictionary my ticket>,
"steam_id": <int Steam ID of connected client>
}
}
Now, we'll set up the signals for authentication callbacks:
func _ready() -> void:
Steam.connect("get_auth_session_ticket_response", self, "_on_get_auth_session_ticket_response")
Steam.connect("validate_auth_ticket_response", self, "_on_validate_auth_ticket_response")
func _ready() -> void:
Steam.get_auth_session_ticket_response.connect(_on_get_auth_session_ticket_response)
Steam.validate_auth_ticket_response.connect(_on_validate_auth_ticket_response)
Next we implement the respective functions for when we receive the signals:
# Callback from getting the auth ticket from Steam
func _on_get_auth_session_ticket_response(this_auth_ticket: int, result: int) -> void:
print("Auth session result: %s" % result)
print("Auth session ticket handle: %s" % this_auth_ticket)
Our _on_get_auth_session_ticket_response() function will print out the auth ticket's handle and whether getting the ticket was successful (returns a 1) or not. You can add logic for success or failure based on your game's needs. If successful, you'll probably want to send your new ticket to the server or another client for validation at this point.
Speaking of validation:
# Callback from attempting to validate the auth ticket
func _on_validate_auth_ticket_response(auth_id: int, response: int, owner_id: int) -> void:
print("Ticket Owner: %s" % auth_id)
# Make the response more verbose, highly unnecessary but good for this example
var verbose_response: String
match response:
0: verbose_response = "Steam has verified the user is online, the ticket is valid and ticket has not been reused."
1: verbose_response = "The user in question is not connected to Steam."
2: verbose_response = "The user doesn't have a license for this App ID or the ticket has expired."
3: verbose_response = "The user is VAC banned for this game."
4: verbose_response = "The user account has logged in elsewhere and the session containing the game instance has been disconnected."
5: verbose_response = "VAC has been unable to perform anti-cheat checks on this user."
6: verbose_response = "The ticket has been canceled by the issuer."
7: verbose_response = "This ticket has already been used, it is not valid."
8: verbose_response = "This ticket is not from a user instance currently connected to steam."
9: verbose_response = "The user is banned for this game. The ban came via the Web API and not VAC."
print("Auth response: %s" % verbose_response)
print("Game owner ID: %s" % owner_id)
The _on_validate_auth_ticket_response() function is received in response to beginAuthSession() when the ticket has been validated. It sends back the Steam ID of the user being authorized, the result of the validation (success is 0 as shown above), and finally the Steam ID of the user that owns the game.
As Valve notes, the owner of the game and the user being authorized may be different if the game is borrowed from Steam Family Library Sharing. Inside this function, you can again put in whatever logic your game requires. You will probably want to add the client to the server if successful, obviously.
Getting Your Auth Ticket
First, when connecting to another peer, you'll want to get an auth ticket from Steam and store it in your pending_members dictionary keyed to the connecting peer's ID; this way you can manage the ticket as needed for various circumstances:
var auth_ticket = Steam.getAuthSessionTicket()
pending_members[peer_id] = {
"my_ticket": auth_ticket
# The peer's ticket will be added later
}
This function also has an optional identity you can pass to it but defaults to null. This identity can correspond to one of your networking identities set up with the Networking Types class.
Now that you have your auth ticket, you'll want to pass it along to the server or other client for validation. Remember to use a different auth ticket per client.
Validating the Auth Ticket
Your server or other clients will now want to take your auth ticket and validate it before allowing you to join the game. In a peer-to-peer situation, every client will want to validate a ticket from every other player. The server or clients will want to pass your auth ticket data, as well as your Steam ID, to beginAuthSession(). For this we'll create a validate_auth_session() function:
func validate_auth_session(ticket_data: PackedByteArray, peer_id: int) -> void:
var auth_response: int = Steam.beginAuthSession(ticket_data, ticket_data.size(), peer_id)
# Get a verbose response; unnecessary but useful in this example
var verbose_response: String
match auth_response:
0: verbose_response = "Ticket is valid for this game and this Steam ID."
1: verbose_response = "The ticket is invalid."
2: verbose_response = "A ticket has already been submitted for this Steam ID."
3: verbose_response = "Ticket is from an incompatible interface version."
4: verbose_response = "Ticket is not for this game."
5: verbose_response = "Ticket has expired."
print("Auth verifcation response: %s" % verbose_response))
if auth_response == 0:
print("Validation successful, adding user to client_auth_tickets")
client_auth_tickets.append({"id": steam_id, "ticket": ticket.id})
# Wait for _on_validate_auth_ticket_response
Note
If using Multiplayer Peer, the peer ID will be different from the client's Steam ID and will not work with beginAuthSession(). Assuming Multiplayer Peer is setup, you can translate the peer ID to the client's Steam ID with the fucntion get_steam_id_for_peer_id() before calling beginAuthSession():
var steam_id = multiplayer.multiplayer_peer.get_steam_id_for_peer_id(peer_id)
If the validate_auth_session() response is 0 (meaning the ticket is valid) a callback will be received and trigger our _on_validate_auth_ticket_response() function which, as we saw before, sends along the Steam ID of the auth ticket provider, the result, and the Steam ID of the owner of the game. At this point you can add the client to your game, or you can send your auth ticket for the client to verify if it has not done so yet. This callback will also be triggered when another user cancels their auth ticket. More on that later.
If you want to authenticate back to the client after their ticket has successfully been validated, generate a ticket and send it back to them while storing your generated ticket in the pending_members dictionary:
var auth_ticket = Steam.getAuthSessionTicket()
pending_members[peer_id]["my_ticket"] = auth_ticket
After both the client ticket is validated and the client has validated your ticket, you'll want to save the player's Steam ID, their auth ticket, and the auth ticket you used for the client in your connected_clients dictionary so they can be called later to cancel the auth sessions. This will keep track of every client that is authenticated, and you can just access your ticket handle for a specific client or their ticket by the user's Steam ID (or peer ID if using Multiplayer Peer).
connected_clients[peer_id] = pending_members[peer_id]
pending_members.erase(peer_id)
Canceling Auth Tickets and Ending Auth Sessions
Finally when the game is over you'll want to cancel all of your auth tickets that were created for each client and end the auth sessions with other players. If a client leaves the game, you just need to cancel the auth ticket created for that client and end that client's auth session.
When the client is ready to leave the game, they will pass all of their auth ticket IDs to the cancelAuthTicket() function like so:
Steam.cancelAuthTicket(auth_ticket.id)
You can iterate through the connected_clients dictionary to cancel all created auth tickets like so:
for peer_id in connected_clients.keys():
Steam.cancelAuthTicket(connected_clients[peer_id]["my_ticket"].id)
This will trigger the _on_validate_auth_ticket_response() function for the server or other clients to let them know the player has left and invalidated their auth ticket. Additionally, you should call endAuthSession() to also close out the auth session with the server or other clients:
Steam.endAuthSession(steam_id)
You will need to pass the Steam ID of every client connected. You can do this in a loop from your connected_clients dictionary like so:
for peer_id in connected_clients.keys():
Steam.endAuthSession(peer_id)
# Then remove this client from the connected_clients dictionary
connected_clients.erase(peer_id)
Note
If using Multiplayer Peer, remember to get the Steam ID from the peer ID to pass to endAuthSession().
The Steamworks documentation states that each player must cancel their own auth ticket(s) and end any auth sessions started with other players.
That concludes this simple tutorial for authenticated sessions.
Additional Resources
Example Project
Later this year you can see this tutorial in action with more in-depth information by checking out our upcoming free-to-play game Skillet on Codeberg. There you will be able to view of the code used which can serve as a starting point for you to branch out from.
Oilyraincloud has an example lobby system that uses GodotSteam and Multiplayer Peer with authentication you can checkout or use as a starting point for your game's multiplayer.