How should client communicate with server to change data in a secure way?

I’m writing a player data system where it stores players data in a Module Script. I came across with a question:

How do I verify a client data is valid or not?

For an example, let’s say a player gets an elimination and the server detects it. The server will require that player’s module script and add 1 elimination value to him/her, makes sense right? But suppose a client will get 1 coin from clicking a UI button, whenever he/she clicks it, the client will request the server using RemoteEvent UpdateData:FireServer("Coin", 1) and update the data in the module script. But how do I garuntee that it is valid? An exploiter could just spam commands to abuse the AddCoin RemoteEvent. Such as UpdateData:FireServer("Coin", 999).

Is this where I have to apply checks such as amount? Note that this is just an example scenario, range check can be applied to check the coin amount in this case, but how about if it’s a true or false value? I want to let the client to update his data sometimes but it will favour exploiters by injecting scripts/commands though.

4 Likes

Name the remote event something the exploiter wouldn’t guess, or change the name of it once referenced. Or, you could add in a keycode into the fireserver event, so you can check if the keycode is included.

When dealing with a client to server communication model, keep in mind that the client should never have authority over the server. Never let the client decide how much of a currency they will receive.

This has to be all done with how a player can earn in-game currency. If a player earns by killing, the server has to see if that player really killed another player. If a player earns by driving, the server has to check how far that player has driven.

As much as possible, let the server give players currency without having to rely on remotes. However there are cases where you may need to depend on the client and remotes because not everything seen locally can be seen by the server. This is where it gets more specific, it depends on your case as to what the player has to do locally in order to earn money. This is where you really have to enforce sanity checks, making sure that everything happening (results and statuses) are within norms.

It’s not easy to give a direct answer to “how should a client communicate with the server” because it’s a very wide and general question. It really comes down to how you setup your systems or your case. If you could provide a more specific scenario as to what the client communicates with the server, we could surely give you a more specific answer.

Edit:
In addition, validating client data is as simple as comparing what the server sees and what the client sees. If there are discrepancies, there may be a fault somewhere, so you have to sync the client with the server, not the other way around.

An example of a client to server communication:

  1. Player A swings sword at Player B
  2. Player A fires a remote
  3. Server gets fired event
  4. Server checks if Player B exists, if Player A was within a reasonable distance from Player B, if Player A was facing Player B, if Player A was holding a weapon, if Player A was dealing the right amount of damage, etc.
  5. Server kills Player B
  6. Server gives Player A money
4 Likes

No, no and no. Please don’t recommend or use this yourself as these are all very easily bypassed by exploiters instead put work into good server checks such as if statements to make sure values are correct, ect.

Also, @Headstackk you shouldn’t be making a remote for giving cash. Handle your secure things via the server always. As an example often people opt for daily rewards to be a chest you touch instead of a UI because the touched event can operate on the server easier.

4 Likes

Actually, let me explain a bit, in my case, I’d like to let the client change his/her equipped weapon. In that case, can I use a RemoteFunction and let the server know to change his equipped weapon? But before that, it will check whether the player owns it or not, and is the weapon valid in a game. If not, return false/error message.

Another case I’m having right now is a bool named IsRespawn is stored inside a server side module script, it determines the player wants to respawn or return to menu, if true, the game will let the player respawn. If false, it will play the menu UI. Is it safe to trust the client for that? For an example:

Client:

RemoteEvent:FireServer(IsRespawn, false)

Server:

PlayerDataModule:UpdateData(DataName, Data)

Module Script:

Data[DataName] = Data
— Also apply checks is Data valid, is it a bool?

Yes always perform the checks on the server and you should be fine just remember the client cant change the first value passed through which is the player so you can use this to your advantage (by that I mean find which player is triggering detections)

Remote events, server-sided sanity checks. Assume that the client is hostile at all times and you need to repel thr bogies on your six.

I came across with another programming logic problem:
Let’s say there’s a table:

Items = {apple, banana, orange}
Data = {x = 1, y = 2, z = 3, item = orange}

In a module script, a client wants to change orange to apple, he invokes server with a request, and wait for a result. Now the server proceed to validate his request, to see whether his requested item is in the Items table or not.

for i,v in pairs  (Items) do
if RequestedItem == v then
Data.Item = v
return "Success!"
else
return "Failed!"
end
end

Here comes the problem, this loop will only proceed one time because it will return a result for the client, is there anyway to solve it?

Due to the way that your tables are assigned, it’s not possible to check without a for-loop.

Items = {"apple", "banana", "orange"}
Data = {x = 1, y = 2, z = 3, item = "orange"}

function check()
  for i,v in pairs(Items) do
    if v == Data.item then
      return true
    end
  end
  return false
end

-- true if valid, false if not
print(check())

Edit: If you wanted to do it manually.

function check(array,value)
  for i,v in pairs(array) do
    if v == value then
      return true
    end
  end
  return false
end

-- true if valid, false if not
print(check(Items,Data.item))
1 Like

Do your checks on both the client and the server, provided the item table is visible to the client.

The client would be responsible for the first layer of things which is to provide immediate response to input. The client will perform all the necessary checks - do they have an item, or do they have enough to buy something, or whatever. If the checks pass on the client, it makes a request to the server to do something. Untampered clients won’t know anything that’s going on here.

The server handles the second layer, which is to validate and perform a secondary check. Think of it like the two-man rule but of a lower-level situation. Your client does the first authorisation from its end or an exploiter bypasses the client-side checks and fakes authorisation. The server now performs a validation and authorises the client’s request as needed, including making changes and informing the client of whether or not the request was accepted.

That aside, if you’re just checking items in a table in general, you can either use the above method of iterating or use table.find which will do that work for you internally. You can then return that the item exists if table.find returns a truthy value.

1 Like

In this case, should I create a server script and use _G for items? Or else I will have 2 same tables in both Module and Local script. I’m not certain because I never use _G so far, only Modules.

I’m thinking of whenever a player wants to purchase/view an item, it will InvokeServer to read the data, which is he/she is able to buy that item or not (Such as levels, currency etc.) the RemoteFunction will only return the result but not writing/overwriting any player’s data. Instead of checking it on client, is this a better approach? since all the data is being stored in server side module script.
My only concern is what if the player happens to have high ping or the server is experiencing lag spikes, will that decrease the overall performance?

If you use a ModuleScript, you would just need to move it to a client-accessible location and ensure it’s pure data that the server and client would be okay with accessing. Avoid _G if you’re already running with ModuleScripts.

If you are making a purchase pane, you should definitely do two way checking. The client should only ask the server to do something important such as purchasing an item and only after client-side checks pass. Otherwise, the client should be responsible for getting item data for the purchase view and determine whether they’re able to buy it or not; do not involve the server in this.

If the server is not performing well, you will experience a notable hit on how long this takes. It’s best to trust the client in this case. Yes, I said trust the client.

Here’s a basic flow if you need it explained:

Setup:

A data module containing the information of some items. This is accessible by both the server and the client. They both use this information respectively to do what they need and it is considered a single point of truth. The client and the server do not directly inform each other of any values and use this info as a reference.


Client dynamic:

The user requests to preview an item. The client will then search for the item in the data module and get the item’s information, then update the Gui accordingly showing the preview of that item.

The user requests to purchase an item. The client checks if the user has enough cash to purchase the item. If not, the user is told that they have insufficient currency to purchase the item. If they have enough, the client sends a request to the server that they want to purchase an item. Note that exploiters can bypass this check or spoof their values so it passes, but we don’t care.


Server dynamic:

The server receives a request from the client to purchase an item. The server assumes that the client passed all its checks and gave itself the okay. The server now takes over from here for the important operations.

The server will, on receiving the request, perform the exact same check that the client does: whether it has enough or not. The server then uses this to determine if it needs to do anything before sending back a response to the client about if the purchase was authorised or not.

If the purchase was successful, then we grant the user the item and subtract currency as needed. If not, then there isn’t a need for us to do anything here - we can just send the client back information that their purchase request was rejected.

The real issue here is that this is not so much how to code this, but rather that it’s a fundamentally exploitable mode of interaction. The user can click a button very fast, at least temporarily (I can do 10+ clicks per second on a mouse vibroing), so the server can’t simply limit clicks – presumably the goal is to click the button as fast as possible after all if you’re allowed to do it as much as you want.

But if you don’t limit clicks there’s no way to stop someone from just using an auto-clicker on their client, and there’s no way code can tell the difference between an autoclicker and them actually clicking without some heuristics, and even those can be defeated.

You have to pick modes of interaction for your game that can be secured in the first place if you want your game to be secure.

How’d I make it accessible by both server and client? And I have to sync the client one whenever the server one is being updated?