I am using rowversion for optimistic concurrency over a set of data: the client gets a batch of data, makes some updates, and then the updates are sent to the database. The simplest solution for managing optimistic concurrency appears to be the one described here: on retrieval, just get the single largest rowversion from the data of interest (or even just the database's last-used rowversion value), and send it to the client. When updates are requested, have the client send the value back, and then ensure that all rows involved in the update have a rowversion value that is less than or equal to the value sent by the client. On update, any row in the database with a higher rowversion than the one sent to the client must have been updated after the initial retrieval, and the user should be prompted to refresh and try again, or whatever the desired experience is.
The problem that seems obvious to me in this is that it would be easy for the client to simply send back UInt64.MaxValue or some other large value and completely defeat this.
After some searching, I've seen quite a few descriptions of solutions that involve sending rowversions to the client to manage optimistic concurrency, but not a single mention of this kind of concern.
Should data values used for optimistic concurrency checking be signed and verified by the server, or perhaps stored server-side in a user session cache or something similar instead of actually sent to the user? Or should the design of an application consider optimistic concurrency checks to be only part of a good user experience and not a security feature - i.e. concurrency checking should only exist to help ensure that users (who should be properly authorized to touch this data in the first place anyways) are making decisions based on fresh data, and the app should function properly even if someone goes out of their way to defeat the concurrency checks?
I'm leaning toward the latter, but it gives me pause to think about apps that use insecure, client-provided rowversion values and just throw user updates blindly into the database without performing any kind of sanity checks on the rows being updated...