A game I was playing was updated recently, and the backend server started to reject the ticket returned from createAuthSessionTicket. There is no change in the payload format, so I'm confused what could possibly cause this.
The backend accepts the session ticket only if it was generated via the game's launcher, and the ticket stops working once the game is closed.
If I login and request an new app ticket via node-steam-user after the game launcher generated its own ticket and BEFORE submitting to the game backend, then the backend server rejects both tickets.
Simplified code example:
// Flow 1: User login
user.on("loggedOn", (details, parental) => {
// user.setPersona(SteamUser.EPersonaState.Invisible);
user.gamesPlayed([APP_ID]);
});
// Prints "Ticket xxx validated by [I:1:1]: OK" when POST request to backend is made
user.on('authTicketStatus', details => {
logger.info(`Ticket ${details.ticketGcToken} validated by ${details.steamID.steam3()}: ${SteamUser.EAuthSessionResponse[details.authSessionResponse]}`);
});
user.on('appLaunched', appID => {
if (appID != APP_ID) {
return;
}
resolve(user); // resolves the promise for Flow 1
})
user.logOn(creds);
// ======== Flow 2 ========= //
// Create app ticket and auth with backend
const user = await loginSteamUserFlow1();
const {sessionTicket} = await user.createAuthSessionTicket(APP_ID);
payload.token = sessionTicket.toString('hex').toUpperCase();
// Backend rejects this call with some vague error message "OAuth exception"
const resp = await client.post(`${GAME_HOST}/session`, {json: payload})
Parsed tickets:
// valid ticket, generated by game
// sessionExternalIP is some random IP everytime
// ownershipTicketExternalIP is not exactly my external IP, but it probably is a previously held IP
// tokenGenerated doesnt seem to be anywhere near the current time
{
authTicket: <Buffer 14 00 00 00 b4 truncated... 2 more bytes>,
gcToken: 'xxxx',
tokenGenerated: 2023-06-02T02:22:25.000Z,
sessionExternalIP: '101.218.247.224',
clientConnectionTime: 2734877,
clientConnectionCount: 4,
version: 4,
steamID: SteamID { universe: 1, type: 1, instance: 1, accountid: xxxx },
appID: 958260,
ownershipTicketExternalIP: 'xxx.xxx.xxx.xx',
ownershipTicketInternalIP: '192.168.1.101',
ownershipFlags: 0,
ownershipTicketGenerated: 2023-05-24T02:05:48.000Z,
ownershipTicketExpires: 2023-06-14T02:05:48.000Z,
licenses: [ 313233 ],
dlc: [],
signature: <Buffer 31 74 f0 b7 truncated... 78 more bytes>,
isExpired: false,
hasValidSignature: true,
isValid: true
}
// generated by node-steam-user, rejected by backend
{
authTicket: <Buffer 14 00 00 00 b1 truncated ... 2 more bytes>,
gcToken: 'xxxx',
tokenGenerated: 2023-06-02T03:28:41.000Z,
sessionExternalIP: 'xxx.xxx.xxx.xxx',
clientConnectionTime: 101,
clientConnectionCount: 1,
version: 4,
steamID: SteamID { universe: 1, type: 1, instance: 1, accountid: xxxxx },
appID: 958260,
ownershipTicketExternalIP: 'xxx.xxx.xxx.xxx',
ownershipTicketInternalIP: '186.129.57.126',
ownershipFlags: 0,
ownershipTicketGenerated: 2023-06-01T07:30:15.000Z,
ownershipTicketExpires: 2023-06-22T07:30:15.000Z,
licenses: [ 313233 ],
dlc: [],
signature: <Buffer 5c 7d 7c 8f truncated ... 78 more bytes>,
isExpired: false,
hasValidSignature: true,
isValid: true
}
Reading the README for node-steam-appticket, it seems that some fields can be spoofed.
Can we specify our own `sessionExternalIP` when requesting & activating app tickets?
Can we specify our own `ownershipTicketInternalIP` as well?
Why is the `ownershipTicketGenerated` by the game launcher so old compared to node-steam-user? Is it cached locally by the Steam client?
Does `user.gamesPlayed([APP_ID])` do anything to affect the validity of the app ticket?
How should I go about reversing the communication of the game <-> Steam ?