Easiest algorithm is as follows:
- When generating a long-lived token (e.g., "remember me"), we generate a pair of random numbers: deviceID and validationID.
- In the user record, store a map of deviceID to Hash(validationID).
- Store deviceID and validationID in the token.
- When we log in, we hash validationID and compare against our stored version.
- When the user logs out, we remove the deviceID from the user record (so any such tokens become invalid).
- When the user changes their password, we delete all deviceIDs.
- Note: We still use expiration times as before.
- Note: We may want to store the expiration time in the user record so that we can clean out old deviceIDs.
- Why store the hash instead of validationID? Defense in depth: if Arc.users database gets stolen, you won't be able to impersonate a user.
- Should we store deviceID data in the user record or have a completely different table? If not, then we also store the username.
- We don't need to do this for short-lived tokens because they expire quickly enough.
- We might be able to expose this to the user to track the set of devices they use. In that case, we probably want to store the deviceID as a long-lived cookie (this is OK, since it is not a secret). We can then add a UI to show the user all their deviceIDs and the last time the signed in on each. We can, of course, offer methods to delete the info on the server. And we might offer a way to name each device.