This is a tricky one, as there are different levels of "secure". Generally when dealing with code that executes on the client, all bets are off, and you have to assume that somebody could change the code that's going to run. When dealing with something like purchasing in-game items, you want the server to be the one that ultimately decides whether or not the player can do that, what it's going to cost, etc. It still makes sense to have the client do some initial validation, so it can provide feedback to the player without having to involve the server, but you don't want the client to be able to say, "Hey, I just bought 999 health potions even though I have 0 gold." The server should see that and say "No, I don't think so, cheater!"
Digging into some of the details on your example, a savvy hacker could absolutely change what their client thinks a product costs. There are various ways they might do this, like modifying the game definition as it's downloaded and before the client sees it, or changing values in memory. The details aren't so important so much as the fact that they control their computer, so if they try hard enough, they can do whatever they want. The important thing is that they can't change what the server thinks a product costs unless you do something like make an event that changes the product's price. The required script that executes on the server creates its own copy of MyAPI
that is separate from the copy of MyAPI
created on the client, so any changes the client might make would not affect the server.
Let's look at a hypothetical situation where a hacker is somehow able to get some code to run on the server for your game. Maybe they shared some community content that had a script hidden in it, and you incorporated that content into your game without realizing the script was there. As your code is currently written, that hacker's script could potentially find an asset reference to your script, call require()
, and then make changes to MyAPI
on the server, substituting their own GetProductPrice
function that makes everything cost 0. There is a way to protect against this, using a Lua metatable and metamethods:
-- Script stored in Project Content
local MyAPI= {}
function MyAPI.GetProductPrice(id)
return 100
end
return setmetatable({}, {
__index = MyAPI,
__newindex = function(table, key, value)
error("Attempt to modify read-only table")
end,
__metatable = false
});
What the heck is going on there? Rather than returning MyAPI
directly, which anybody could then modify, the code above is returning an empty table (the {}
passed as the first parameter to setmetatable
), with a custom metatable set on it. Metatables are a Lua concept that controls how a table functions, through the use of one or more metamethods. In this case, we set an __index
metamethod that is called any time somebody tries to look up a key that doesn't exist. (Since it's an empty table, this will be always.) We've given it MyAPI
as a fallback table to look things up in. We've also set a __newindex
metamethod that is called any time somebody tries to insert a new key into the table. (Again, since it's an empty table, this is always.) That function fails with an error, preventing anybody from being able to add new keys. Finally, the __metatable
entry prevents anybody (including you!) from being able to call getmetatable
or setmetatable
on the table you're returning, so nobody can tamper with what you've put in place. It's important to note that this metatable only affects the empty table we're returning in place of MyAPI
. MyAPI
itself can still be modified by anything that has a direct reference to it, but that should only be your script.
Is all of this worth it? That's up to you. Even with something like this in place, persistent or clever hackers may be able to find some other vulnerability if they're able to sneak a script into your game. Or if you're careful enough, you can prevent them from being able to execute code on the server, and then this becomes unnecessary. This sort of thing can help keep you from accidentally modifying MyAPI
from other scripts by accident, so there can be other benefits. I think it really comes down to personal preference and coding style.