1

I've created a simple Node.js app using Express.js and socket.io (available here), where the user clicks a button, and it increments a number on the page. This number is also incremented live among all clients connected to the page. I am using web sockets and socket.io to get the client-server communication and live number updating system.

I am using the flood-protection module to limit socket emits to 5 per second, but this really doesn't make the game very fun because of the low amount of clicks per second you can have, and hackers could just use a setInterval and still make considerable progress automatically, even at such a low rate.

My issue:

  1. I don't want the user to have to authenticate themselves - anybody should be able to play!

  2. I want to keep the click rate around 15 clicks per second, if possible.

  3. I don't want people to be able to send socket messages and automatically click the button from the browser console.

Here's the program:

index.js

var express = require("express");
var http = require("http");
var socketIO = require("socket.io");
var path = require("path");
var fs = require("fs");
var FloodProtection = require("flood-protection").default;
__dirname = path.resolve();

function btoa(str) {
  return new Buffer(str, 'latin1').toString('base64');
};

function atob(b64Encoded) {
  return new Buffer(b64Encoded, 'base64').toString('latin1');
};

var app = express();
app.get("/", function(req, res){
  res.sendFile(__dirname + "/index.html");
});

var temp;
num = temp | parseInt(atob(fs.readFileSync("num.txt"))) | 0

var server = http.createServer(app);
var io = socketIO.listen(server, {log: true});
io.sockets.on("connection", (socket) => {
  protector = new FloodProtection({rate: 5, per: 1})
  io.sockets.emit("update", num);
  socket.on("push", (value) => {
    if (protector.check()) {
      num++;
      temp = num
      io.sockets.emit("update", temp);
    } else {
      io.sockets.emit("update", "You can only click the button five times per second.")
      socket.disconnect(2)
      setTimeout(()=>{}, 3000)
    }
  });
  socket.on("disconnect", () => {
    fs.writeFile("num.txt", btoa(String(temp)), (err) => {
      if (err) throw err;
      console.log("saved | new num: " + temp);
    })
  })
});

server.listen(5000);

index.html

<html>
  <head>
    <title>A Button</title>
  </head>
  <body>
    <button onclick='push();'>Click me!</button>
    <p id="out"></p>
  </body>
  <script type="text/javascript" src="/socket.io/socket.io.js"></script>
  <script type="text/javascript">
    var variableFromFrontEnd = 2;
    var socket = io.connect("/");
    socket.on("connect", function() {
      socket.on("update", function(val) {
        document.getElementById("out").innerHTML = val
      });
    });
    socket.on("disconnect", function() {
      setTimeout(()=>{socket.connect();}, 1000);
    });
    function push() {
      if (socket.connected) {
        socket.emit("push");
      }
    }
  </script>
</html>

num.txt is a base-64 encoded number.

So, is there a way to be able to do this without significant rate limiting or authentication? Or am I just going to have to use rate limiting?

miike3459
  • 1,431
  • 2
  • 16
  • 32
  • I wonder if the upvoter is the person I'm watching play my game right now ;) – miike3459 Dec 31 '18 at 19:45
  • Yes it's me. I reached 420 which was my goal B) Ok so I tried different client side things. I could set timeout as you said and play with loops. I carried a small research and I found out few things. Facebook security engineer implement a message just to discourage others (https://stackoverflow.com/questions/21692646/how-does-facebook-disable-the-browsers-integrated-developer-tools ). Unfortunately JavaScript is full client side and I don't think you can disable in full. https://davidwalsh.name/disable-console read this. I also want to see other answers so I put a star on your good question – Iulian Dec 31 '18 at 20:06
  • @Cristian Ok, just confirming. The number probably reset at around 346; that was my bad, I accidentally refreshed the page or lost connection or something. Should I try what was attempted on that Facebook question? – miike3459 Dec 31 '18 at 20:12
  • Personally I don't recommend it if it's for something very serious. It seems that facebook does that on Chrome and other browsers may still have access to calling your button in the console. Therefore, let's see if someone suggests any other frameworks or solutions to your problem. Facebook's solution is rather temporary – Iulian Dec 31 '18 at 20:25
  • @Cristian Yeah, I couldn't get any of then to work in firefox anyway. – miike3459 Dec 31 '18 at 20:27
  • 1
    @Cristian I think I've come up with a solution. It requires you to pass in a `new Error()` with `socket.emit` and the server checks the stack trace to ensure that it doesn't contain any defaults indicating a browser console. – miike3459 Dec 31 '18 at 21:39
  • when I click the button it doesn't work for me. – Iulian Dec 31 '18 at 21:50
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185991/discussion-between-connectyourcharger-and-cristian). – miike3459 Dec 31 '18 at 21:51
  • if you wanted to authenticate users, what would you use tho? – Mr-Programs Jan 16 '19 at 01:19

1 Answers1

1

There's a lot of different ways for users to cheat, and just as many ways to prevent them. The general rule is that you can only "make things harder" (if you're lucky, hard enough that the potential cheater loses interest before succeeding).

For a browser-based game, I would make sure that you are at least ensuring your game gets totally minified/tersed (so your javascript code is as unreadable as possible, and it's more difficult to just call a "click" function directly), and build in checksums in your messages (so the user can't just make socket calls directly to the server).

Once you've done that, you still have to deal with users who generate click events on the element directly with code or a plugin, or users who use a program outside the browser to generate click events above the button repeatedly. Your best defense against this is not to prevent it, but instead to detect it -- probably on the server side, by watching for users that have a sustained click rate that is not humanly possible, and then blowing up their game / temporarily banning their IP / etc.

See a related question Ways to prevent or reduce cheating for more related ideas (this question is about general client-server games, not browser games, but some of the discussion is still useful).

Elliot Nelson
  • 11,371
  • 3
  • 30
  • 44
  • Hi, regarding your comment about checksums, how would I do that? Could you possibly reference me to a resource? – miike3459 Jan 03 '19 at 22:55
  • Generally a checksum is something you append to your message that can be checked on the other side, ranging from something cryptographic (like an MD5 hash) or something simple (add all bytes modulo 32 bits). A great place to start with concepts like this might be https://gafferongames.com/categories/building-a-game-network-protocol/ - he covers all kinds of interesting tricks and strategies. – Elliot Nelson Jan 04 '19 at 00:40