It’s a fact of life: some users of your game are going to try and cheat. There’s no getting around it, especially in multi-player games.
Put yourself in your users’ shoes: you just downloaded a new game, and you’re super excited to play it, but when you look at the global leaderboard, you see something like the image to left. Everyone has insane, super high scores. It sucks! You’re discouraged before you even start.
Not only might you have users fleeing your app, it might compromise the integrity of your game. If you’re running tournaments where prizes are given to top users, no one is going to play if the only people winning prizes are cheaters. So, how do you combat this?
While there’s no one silver bullet, there’s a lot you can do on your backend to help detect and deter cheaters. Here are a few of my favorites.
1. Use SSL on all of your endpoints
This one is a no-brainer, no matter what kind of app you have. Using SSL makes it much harder for anyone to intercept and change traffic on its way from the app to the server, and vice versa. At the cost of ~$15/year, it’s well worth it. Sure, cheaters can still find ways around SSL, but given how easy it is to implement, it’s something every app should use.
2. Sign requests with a score key
Your server is sending scores to your backend. Maybe you have an endpoint that receives parameters like this:
score=99&user_id=1234
Pretty straightforward. But what if someone discovers what this endpoint is? It then becomes trivial for them to cheat: just make a fake request with a higher score parameter. How can you make your request more difficult to impersonate? The key is signing your request, with what I like to call a “score key”.
There are many ways to implement a signed score key, but the easiest way is by using a shared secret. You’ll want to have some randomly generated string, something like “augxeuufwq5J5pJy”, which will be stored on your server and also in your app code. Now, when you send up a score, your app will generate a SHA1 hash of your parameters and this key. Let’s say the pattern you decide on is score|user_id|shared_secret
. For our example above, that means you’ll be using "99|1234|augxeuufwq5J5pJy"
which results in a hash value of "5ab2d9c6174667bde032dc5edf6b77b3dc0f676e"
.
Now, start appending that to your request parameters:
score=99&user_id=1234&score_key=5ab2d9c6174667bde032dc5edf6b77b3dc0f676e
On the server, you’ll try to generate the exact same score_key
, using the passed in parameters. If the server-generated score key matches what’s passed in, you can trust the score. If it’s different, someone’s trying to cheat.
Because of the way hashing works, even if someone sees the request to the server, they won’t be able to change any parameters, unless they know both the shared secret and the pattern you use for generating. So keep those secret!
3. Don’t allow the same request twice
Imagine for your game, you track the user’s current inventory in your backend. You might be making a call like this:
http://api.mygame.com/resources/ammo
You will likely make a POST
call to this endpoint, and the server adds the ammo to their inventory. But, similar to the score endpoint, what if someone discovers this URL? They can then call it as many times as they want, adding more and more ammo each time.
Instead, you need to figure out some sort of verification system, and that’s going to depend a lot on your specific game. In this case, let’s say a player gets ammo when they pick up a specific box in the game, that can only be picked up once. Rather than using an ammo endpoint, how about an endpoint to claim that box?
http://api.mygame.com/resources/box123/claim
Once the player picks up the box in the game, your app should call this endpoint, which will mark box123
as claimed on the server and will award the ammo to the user. If they try to claim it again, the server will know that it’s not available any more, and it won’t award them any more ammo.
4. Add moderation tools to your admin and ban repeat offenders
If you have any decently sized game, you’re going to want an admin tool to make it easy to remove scores and ban repeat offenders. It doesn’t have to be anything complicated: just create a web interface where you can go in and view the current scores. When you see a user who seems to be cheating, you’ll want to be able to do each of these actions:
- Flag score: This just marks the score as suspect, but doesn’t actually affect gameplay—the user will still be present in the leaderboards. Flagging the user is a nice way to be able to mark someone who you suspect might be cheating, but you want to watch them further.
- Hide score: Hiding a score means it’s removed from the leaderboard. The user won’t be visible to anyone else, but they’ll still be able to play the game—they just won’t be able to compete against anyone. You’ll need to update your score processing code to take this into account.
- Ban user: Banning a user will remove them from leaderboards and also not allow them to play the game any more (at least not the multiplayer aspect). This should be reserved only for the worst cheaters.
5. Throw out impossible/unrealistic scores
This one depends a lot on your specific game, but it can be very useful as a way to get rid of 90% of your cheaters: check scores as they come in and make sure they’re realistic. If they are, save them. If not, throw them out.
How do you determine if a score is realistic? If your game has a definite maximum score that a user can get, that’s an easy place to begin. However, you’ll probably want to look at the statistics of how scores are distributed: if a user has a score that is way above the average score, you’ll probably want to flag it, so someone can go and take a look.
In addition to just looking at the scores, you should also track how many times a user has played a given level or game—if they get a really high score on their first try, they’re probably cheating, and you can toss their score out or ban them.
6. Enforce time windows serverside
If your game has any sort of timed play, such as tournaments, you’ll want to make sure you’re enforcing the time windows on the server. Your app likely already does this: once the tournament is over, it doesn’t let the user play any more. However, the user could be manipulating their device’s clock, making it possible to play for longer than you allowed.
To combat this, it’s pretty simple: make sure you’re doing time checks on the server as well. Even if the user finds a way to get the game to submit scores late, the server will always have the correct time, and can just reject these late scores.