Steam Authentication - Invalid Ticket Error 101

I’m having issues with integrating Steam authentication into my project. Initially it was sporadic, but as of now it is a constant issue. Steam is loading and verifying correctly however when I pass the ticket to Nakama I get a 101 Invalid Ticket error. Previously I was retrying the ticket authentication up to three times and it would occasionally work however now I cannot get Steam to authenticate at all. The publisher key and AppId are both valid and updated.

Godot function that gets auth ticket and passes to Nakama

func validate_auth_session_async(steam_id: int) -> int:
	_auth_ticket = Steam.getAuthSessionTicket()
	var response: int
	var auth_response: int = Steam.beginAuthSession(_auth_ticket.buffer, _auth_ticket.size, steam_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": _auth_ticket.id})
		var auth_ticket_string := ''
		for i in range(_auth_ticket['size']):
			auth_ticket_string += "%02x" % _auth_ticket['buffer'][i]
		
		response = yield(_authenticator.authenticate_steam_async(auth_ticket_string), "completed")
		if response == 0:
			_storage_worker = StorageWorker.new(_authenticator.session, _client, _exception_handler)
		else:
			print("Auth error: %s" % response)
	return response
	# You can now add the client to the game

Nakama Error in Console

dev-nakama-1       | {"level":"debug","ts":"2024-06-20T03:44:53.118Z","caller":"social/social.go:633","msg":"Error returned from Steam after requesting Steam profile","errorDescription":"Invalid ticket","errorCode":101}
dev-nakama-1       | {"level":"info","ts":"2024-06-20T03:44:53.118Z","caller":"server/core_authenticate.go:764","msg":"Could not authenticate Steam profile.","error":"Invalid ticket, 101"}
  1. Versions: Nakama {1.30.1 / 3.20.0}, {Docker}, {Godot 3.5.3}
  2. Server Framework Runtime language (If relevant) {Go, Lua}

Found the solution here. As this is a bit of a mess of functions I’ll go into a bit of detail here in case anyone else is trying to connect Godot, Steam, and Nakama together in the future.

For context: I am using the GodotSteam plugin for Godot 3.5.3. I’m sure this would work the same or similarly for the Steam-built editor.

The core issue I was running into was stepping on the toes of the callbacks and signals.

During the game start, I check if Steam is enabled and logged in. If it is, I make a single function call to my ServerConnection singleton which calls Steam.getAuthSessionTicket().

func initiate_steam_connect() -> void:
	print("Fetching ticket")
	_auth_ticket = steam.Steam.getAuthSessionTicket()

This triggers _on_get_auth_session_ticket_response which if successful (result is 1), the validate_auth_session function is called.

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)
	if result == 1:
		validate_auth_session(_auth_ticket, steam.Steam.getSteamID())

I modified the validate_auth_session function as below.

func validate_auth_session(ticket: Dictionary, steam_id: int) -> void:
	#_auth_ticket = steam.Steam.getAuthSessionTicket()
	var auth_response: int = steam.Steam.beginAuthSession(_auth_ticket.buffer, _auth_ticket.size, steam_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": _auth_ticket.id})
		var auth_ticket_string := ''
		for i in range(_auth_ticket['size']):
			auth_ticket_string += "%02x" % _auth_ticket['buffer'][i]
		print("Got auth ticket: %s" % auth_ticket_string)
		var response = yield(_authenticator.authenticate_steam_async(auth_ticket_string), "completed")
		if response == 0:
			_storage_worker = StorageWorker.new(_authenticator.session, _client, _exception_handler)
			emit_signal("steam_connected")
		else:
			print("Auth error: %s" % response)

While I don’t use the function for anything in this case, _on_validate_auth_ticket_response is where I believe you would send the ticket onwards for validation by clients in a P2P scenario.

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)

Hope this helps anyone else who comes across this issue.

1 Like