The Auth0 APIs do not provide any means of performing writes with 100% reliability - i.e., with no possible race conditions. Concurrency controls on the client application side can reduce the changes of race conditions on writes to Auth0, but they cannot completely eliminate them. The only way to eliminate them would be for Auth0 APIs to support conditional writes - e.g., ETag/If-Match headers, or update only if higher version number or later timestamp. ETag/If-Match headers requires a GET before update, but conditional update based on version number of timestamp allows updates to be performed without first performing a GET.
Consider an update to a user’s email address. We need to handle the email verification ourselves in the client application before updating Auth0 to ensure the email is verified before applied to the user’s account. Once the user verifies their email, we queue a request to update Auth0 (in the same database transaction, using the “transactional outbox” pattern). The outbox handler dequeues the message, updates Auth0 and deletes the message.
However, a user can queue two email changes quickly, which creates a race - the latest email change might be beaten by the previous email change. We can put a UUID on the database record that identifies the change and only process the outbox message if the change ID in the message matches that of the database record. That considerably reduces the risk of a race condition, but it doesn’t eliminate it. It can’t. It’s mathematically impossible to eliminate the race condition unless the Auth0 API supports some kind of conditional write.
For example:
- User changes email from A to B.
- User verifies B. The system enqueues a request to update Auth0 to B.
- The request handler checks the change ID on the request matches the database record. It does. It goes to make the change, but then the thread is suspended for some reason (e.g., very long GC pause).
- The request handler loses its database connection and releases the request message.
- Another server/pod picks up the request, checks the change ID is the same (it is), sends the request to Auth0 to change the email to B, clears the change ID field and deletes the request.
- The user changes their email from B to C.
- The user verifies C. The system enqueues a request to update Auth0 to C.
- The request handler picks up the request, checks the change ID is the same (it is), sends the request to Auth0 to change the email to C, clears the change ID field and deletes the request.
- The suspended thread on the original server/pod resumes and sends the request to Auth0 to update the email to B.
So there is currently no 100% reliable way to use the Auth0 API to perform writes.
I would hope that supporting conditional writes would be a relatively minor change that would be very impactful in terms of system reliability.