I have a web based project that allows users to work both online and offline and I'm looking for a way to generate unique ids for records on the client side. I'd like an approach that works while a user is offline (i.e. unable to talk to a server), is guaranteed to be unique, and is secure. By "secure", I'm specifically worried about clients submitting duplicate id's (maliciously or otherwise) and thereby wreaking havoc on data integrity.
I've been doing some googling, hoping this was already a solved problem. I haven't found anything that's very definitive, especially in terms of approaches that are in use in production systems. I found some examples for systems where users will only access the data that they've created (e.g. a Todo list that's accessed on multiple devices, but only by the user who created it). Unfortunately, I need something a bit more sophisticated. I did find some really good ideas here, which are in line with how I was thinking things might work.
Below is my proposed solution.
- IDs should be globally unique (or at least unique within the system)
- Secure (as outlined above and otherwise)
- Data can be viewed/edited by multiple users, including users who didn't author it
- Doesn't cause significant performance issues for backend db's (such as MongoDB or CouchDB)
When users create an account, they would be given a uuid which was generated by the server and known to be unique within the system. This id must NOT be the same as the users authentication token. Let's call this id the users "id token".
- Identify this as an "insert" action (i.e. not an update or a delete)
- Validate both parts of the composite key are valid uuids
- Validate that the provided "id token" part of the composite id is correct for the current user (i.e. it matches the id token the server assigned to the user when they created their account)
- If everything is copasetic, insert the data into the db (being careful to do an insert and not an "upsert" so that if the id does already exists it doesn't update an existing record by mistake)
Queries, updates, and deletes wouldn't require any special logic. They would simply use the id for the record in the same manner as traditional applications.
What are the advantages of this approach?
Client code can create new data while offline and know the id for that record immediately. I considered alternate approaches where a temporary id would be generated on the client which would later be swapped out for a "final" id when the system was online. However, this felt very brittle. Especially when you start thinking about creating child data with foreign keys that would also need to be updated. Not to mention dealing with urls that would change when the id changed.
By making ids a composite of a client generated value AND a server generated value, each user is effectively creating ids in a sandbox. This is intended to limit the damage that can be done by a malicious/rogue client. Also, any id collisions are on a per user basis, not global to the entire system.
Since a users id token is tied to their account, ids can only be generated in a users sandbox by clients that are authenticated (i.e. where the user successfully logged in). This is intended to keep malicious clients from creating bad ids for a user. Of course if a users auth token were stolen by a malicious client, they could do bad things. But, once an auth token has been stolen the account is compromised anyhow. In the event that this did happened, the damage done would be limited to the compromised account (not the entire system).
Here are some of my concerns with this approach
Are there any loopholes that I'm missing which could allow a malicious user to compromise the system?
Is there reason to worry about DB performance when querying for a composite key made up of 2 uuids. How should this id be stored for best performance? Two separate fields or a single object field? Would there be a different "best" approach for Mongo vs Couch? I know that having a non-sequential primary key can cause notable performance issues when doing inserts. Would it be smarter to have an auto generated value for the primary key and store this id as a separate field? (this question now has a separate SO question of its own)
With this strategy, it would be easy to determine that a particular set of records was created by the same user (since they'd all share the same publicly visible id token). While I don't see any immediate issues with this, it's always better to not leak more info about internal details than is needed. Another possibility would be to hash the composite key, but that seems like it may be more trouble than it's worth.
In the event that there is an id collision for a user, there's not a simple way to recover. I suppose the client could generate a new id, but this seems like a lot of work for an edge case that really shouldn't ever happen. I'm intending to leave this unaddressed.
Only authenticated users can view and/or edit data. This is an acceptable limitation for my system.
Is above a reasonable plan? I realize some of this comes down to a judgement call based on a fuller understanding of the application in question.