UUID locks
Understand how VoterGuideOS protects anonymous voter choices and personal information with UUID locks.
VoterGuideOS uses UUID locks to keep voter-specific ballot data private for anonymous users. Protected data includes:
- Race and measure choices
- Likes and decision factors
- Personal information submitted with a ballot:
name,firstName,lastName,email, andphone
A ballot ID identifies a ballot, but it does not authorize access to this protected data.
Why UUID Locks Exist
Ballot IDs may appear in shared links, browser history, logs, or support workflows. Anyone with a ballot ID can retrieve the public ballot content, but they must not automatically receive the voter's choices or personal information.
A uuidLock is a random value generated for an anonymous voter session and stored on the user's device. When the same lock is supplied later, the API can return or replace only data associated with that lock.
Important
Treat a UUID lock as a secret. A missing or incorrect lock never reveals another anonymous session's protected choices or personal information.
Authenticated ballots do not require a UUID lock. Their protected data is associated with the authenticated response and is returned only through authorized requests.
Generate the Lock Before Creating a Ballot
Generate one UUID lock per local anonymous voter session and persist it before creating the ballot.
const uuidLock =
localStorage.getItem('uuidLock') ?? crypto.randomUUID();
localStorage.setItem('uuidLock', uuidLock);Reuse this value for every anonymous create, update, choice, and read request involving that ballot.
Personal Information
Continue sending personal information through the normal flat ballot fields. Do not send an internal protected-field structure.
For an anonymous ballot, include uuidLock as a query parameter when creating or patching the ballot:
const ballot = await client.service('ballots').create(
{
street: '730 Peachtree St NE, Atlanta, GA 30308',
email: 'voter@example.com',
firstName: 'Taylor',
lastName: 'Jordan'
},
{
query: {
uuidLock
}
}
);Anonymous personal information sent without a UUID lock is ignored. The response may include these presence indicators without revealing the protected values:
hasEmailhasNamehasPhone
Choices
The Choices API accepts the UUID lock in the request body:
await client.service('choices').create({
ballot: ballot._id,
race: raceId,
candidate: candidateId,
uuidLock
});Saving another choice with the same ballot, race or measure, and UUID lock replaces only that anonymous session's matching choice.
Retrieve Protected Data
Include the UUID lock when requesting an anonymous ballot by ID:
const ballot = await client.service('ballots').get(ballotId, {
query: {
uuidLock
}
});With a matching lock, the response can include the session's flat personal-information fields and its protected choices. The internal protected PII storage structure is never returned.
If the lock is missing or incorrect, public ballot content is still returned, but the anonymous session's choices and personal-information values are omitted.
Claiming an Anonymous Ballot
When an anonymous voter signs in or creates an account, include the same UUID lock while associating the ballot with the authenticated user. Matching protected choices and personal information are promoted to the authenticated response.
After the ballot is claimed, authorized authenticated requests do not need the UUID lock.
Recommended Storage and Security
Store UUID locks in device-local storage, such as:
localStoragesessionStorage- IndexedDB
- Secure mobile app storage
Do not share UUID locks between users, expose them in public links, or write them to logs or analytics. Generate them with a cryptographically secure UUID implementation.
