tl/dr: Although you could store the balance in your JWT and it would
be safe from tampering, there are many ways in which the balance in
the JWT might be technically valid but still incorrect. Therefore, you are probably best
off checking the balance in the database everytime, and you certainly
need to check it before making any transactions
There are two kinds of JWTs:
- JWS: Payload is in "plain text" and has a signature to confirm its contents
- JWE: The payload is completely encrypted.
These have slightly different use-cases. If all you need to do is verify that the data stored in the JWT is correct and has not been tampered with, then a JWS is fine (presuming you implement it properly and verify the signature on all requests). Therefore, you could store the balance in a JWS and later confirm that the reported balance is what you originally stored.
If you also want to keep the data private, then you can use a JWE. The encryption will also guarantee that the data is not modified (again, assuming you properly implement the JWE). Note that the only person who normally has access to the JWT is the end user, so we're talking about keeping it private from them - probably not necessary for your use case (h/t EdC).
Therefore, the balance is safe from tampering.
In either case you should check the balance with the database before making an actual transaction though because:
The balance might still be wrong. If another transaction was made without updating a JWT, or if an old JWT is presented (aka a replay attack), then you can have a valid JWT that has the incorrect balance. This is very likely even without active attackers. Consider a user who uses more than 1 app. In your hypothetical scenario, what happens if a balance is stored in a JWT in one device, and then the user logs into another device and makes a transaction? The data in the JWT for the first device is now incorrect, even though the JWT itself is valid and has not expired. This is just one of many ways in which valid but incorrect JWTs may happen.
There are many possible ways to fix issues with outdated data in a JWT, but in the end those fixes might be more work then simply checking the database every time. However, that's a balancing act that ultimately is up to you! There isn't one right answer there.