Glenn Fielder wrote an awesome post the other day on how big name first person shooters prevent cheating. It’s not a long read, so I recommend you go over and read the whole thing. In essence: rather than letting the game tell the server what’s happening, the server is actually running the entire game itself. Instead of the console telling the server “Player 1 shot Player 2”, it just sends up “Player 1 pressed the fire button 5 times”. The server knows everything about where the player is standing and facing, so it can figure out the rest. If pressing fire 5 times means Player 1 hits Player 2, the server tells the client that’s what happened, not the other way around.
But what does this have to do with your mobile app? On the surface, not a whole lot. Very few apps are anywhere near the scale of a top tier FPS, and cheating or faking user actions probably isn’t very rampant. However, the mantra of “never trust the client” still applies, even if the actual implementation doesn’t. Even though you might have written every line of code in your app or game, you still can’t trust it—what if a user hacks it to be able to send up bogus information, or they discover what API calls the app is making and modifies them? Your server needs to be able to handle this. It needs to ultimately be the source of truth and needs to be able to tell good from bad.
How about an example? Say for instance, you have a mobile game, and your backend stores your users’ high scores. To keep things simple, you have a high_score
field for each user, where you save each user’s best score. Updating it is pretty simple: your app sends up a high score, and your server saves it. Easy.
What if your game supports multiple devices? Your user might be playing your game on his iPhone and his iPad. Imagine he hasn’t used his iPad in a couple months, but he’s been steadily increasing his score on his iPhone: he’s now reached 1,000,000 points. One day, he turns on his iPad, and your game sends up what it thinks is his best score: 500,000 points. What does your server do?
If all your server has been doing is taking the user’s provided high score and updating the database, all of his progress is wiped out, and he’s back to 500,000 points again. This is what can happen you’re blindly trusting your app! There’s no nefarious actor in this: it’s just your user turning on an old device, but you can probably imagine how bad things can turn out if it actually was someone trying to cheat your system.
So, how do you solve it? In this particular case, all you really need is a simple validation to make sure the new score that’s being passed up is higher than the old score. If it is, save it to the database. If not, ignore the lower score. You’re no longer completely trusting the client, and your game is more stable and reliable than it was before.
Obviously, this is a really simple example. What happens when you’re running a game with thousands (or even millions!) of players, and they’re all competing against each other? You’re almost certainly going to have some people trying to game the system and hack their score. While the specifics of what you do to prevent cheating might change, the underlying principle stays the same. Whether it’s something relatively simple, like validating that the score they send up is actually possible, or it’s something incredibly complex, like Glenn Fiedler’s examples in the linked post, it all comes down to never trusting the client, even when you built it yourself.