It's impossible to safely grant rewards for gamepasses bought in-game

As a Roblox developer, it is currently impossible to safely grant rewards to a player for a gamepass they have just purchased in-game.

Historically, developers have used PromptGamePassPurchaseFinished to detect in-game pass purchases and grant rewards. However, it has been revealed that clients may be able to spoof this event and theoretically unlock every gamepass in the game for free, making this event unsafe to rely upon.

Ideally this could be fixed with a simple safety check with UserOwnsGamePassAsync, but the documentation explains that due to its caching behavior, it would not provide a reliable result:

If the pass is purchased in-experience through PromptGamePassPurchase(), this function may return false due to the caching behavior.

One potential solution to this problem would be to clear the cached value immediately before firing PromptGamePassPurchase, allowing the developer to reliably check for ownership.

If Roblox is able to address this issue, it would improve my development experience because I would be able to let my players buy gamepasses in-game and enjoy the rewards without having to leave and join a different server.

24 Likes

I second this.

Currently, some creators rely on this event to grant perks, and have suffered from exploiters as they were unaware of the security risk. The official documentation even supports this use case.

I’m aware we shouldn’t discuss implementation, but I would imagine a server-side check (non-cached call if WasPurchased==true) for PromptGamePassPurchase is a good solution for this problem.

Affected methods:

  • SignalPromptProductPurchaseFinished
  • SignalPromptGamePassPurchaseFinished
  • SignalPromptBundlePurchaseFinished
  • SignalPromptSubscriptionPurchaseFinished
  • SignalPromptPurchaseFinished
5 Likes

We were told back in August of last year that they are working on improving the purchase completed events, and then told they were still working on it in December:

This was to address a separate issue where purchases were not processed at the time of purchase, but rather when the client pressed “OK” which can be attributed to this issue as well.

@SplootingCorgi Are there any updates you can provide on this? With these highly severe vulnerabilities being exposed just now and the amount of developers that rely on these events to handle critical purchases, it’s needed now more than ever.

Related thread that I think should be mentioned here on behalf of @xChris_vC:

2 Likes

I’m assuming this applies server-side, so if I were to listen to this event on the server, it could be called by exploiter with wasPurchased set to true, essentially enabling them to gain access to every paid item in the game.

Basically equivalent of this would look something like this:

-- server.lua
remote.OnServerEvent:Connect(function(player, gamePassId, wasPurchased)
  if wasPurchased then
    print(`Granted {player.UserId} perks for {gamePassId}`)
  end
end)
-- client.lua
remote:FireServer(000000000, true)

If I am understanding this correctly, this is insane vulnerability that affects some of the games that I worked for, because there is no mention anywhere that it relies on client provided values, and one would expect that it would be invoked by trusted Roblox server, or at least valided on the server.

3 Likes

Hey!

I might have a potential solution to this problem as well as UserOwnsGamePassAsync

You might want to use HttpService to fetch data from the inventory.roblox.com/…

I am pretty sure that the API does not cache the data unlike UserOwnsGamePassAsync, however, be aware of rate limitation though. I believe this might be the solution which is effective for reliably checking if the user owns the gamepass, but might be ineffective overall due to the use of the API to do so.

If you are rich, you might well host your own web server, create your own service which will fetch data from Roblox APIs (and use proxies to avoid rate limit). From there, you can use your own web server from the game to check if the user owns the gamepass.

1 Like

This is a good solution which I think(or hope?) most of those in this thread are already aware of. However, I think the goal of this feature request is to fix the vulnerability itself because the common idea here is that the purchase finished signals return secure and correct information that does not need re-validation, and thousands of developers have already implemented this logic in their game without the extra required checks, being unaware of the sheer insecurity of the system.

2 Likes

I agree with you!

The solution I mentioned should only be used as temporary workaround until the issue is addressed (given that Roblox takes really long time to fix issues). :+1:

2 Likes

The issue you’re presenting here is not quite what you think it is. Users cannot spoof their purchased state if they do not own the gamepass.

They can, however, fire the listed PromptGamePassPurchaseFinished event multiple times with successful arguments for a gamepass they already own. There’s no restriction on this, it’s been known about for a very long time, and it’s why this notice is provided within its documentation:

To avoid duplicate item grants, store the purchase in a data store. If multiple attempts are made, check if the item has been already granted. For repeatable purchases use developer products.

As long as your purchase logic checks to see if the user’s already received their perks, you’re good to go. Gamepasses shouldn’t be set up with multiple purchase logic in mind, you should use developer products for that.

3 Likes

This workaround cannot feasibly work for everyone. Please fix the security issues and don’t make us rely on HttpService.

(No shame to the person who posted the workaround! I only added my two cents because the lurking system engineers may take this as a solution)

5 Likes