No Account Required: Securing Persistent Browser Session Logs for Tabletop RPG Dice Rollers
The Misconception About Browser Session Logs for Dice Rollers
Most tabletop RPG groups running play-by-post or Discord-integrated games have settled into the same broken habit. They use a browser-based dice roller for a session, screenshot the results, paste them into a chat channel, and then lose the browser tab. When a dispute arises two days later about whether that critical hit actually landed, the only evidence is a cropped image that could have been edited in thirty seconds.
The assumption driving this behavior is that without an account, there is no reliable way to persist dice roll logs across browser sessions. So players either resign themselves to fragile screenshots or reluctantly create accounts on platforms they do not want to be tied to. Both responses are built on a misunderstanding of what modern browsers can do locally.
Here is the correction: you can secure persistent, tamper-evident dice roll logs in a user's browser without requiring any account, any server-side storage, or any login flow. The browser itself provides the storage layer. Cryptographic primitives available through the Web Crypto API provide the integrity layer. What is missing in most dice roller implementations is simply the wiring between those two things.
This article walks through that wiring step by step, from the perspective of a specific workflow: a play-by-post Dungeon Master who needs to maintain a verifiable roll history across multiple players, multiple sessions, and multiple weeks, all without forcing anyone to register an account.
Step 1: Understand What "Persistent" Actually Means Without an Account
Persistence without an account does not mean "the data exists somewhere in the cloud." It means the data survives a browser restart, a tab close, and even a system reboot, because it is written to a storage layer that the browser controls locally.
There are three storage mechanisms available in every modern browser, and they behave differently:
sessionStorage
This survives page reloads within the same tab but is destroyed the moment the tab closes. It is useless for cross-session dice log persistence. Many dice rollers use it by default because it is the simplest option, which is why roll history vanishes when the tab is lost.
localStorage
This persists across tab closes and browser restarts. It has a typical limit of 5 MB per origin in most browsers, which is more than sufficient for dice roll logs. A single roll entry, stored as compact JSON, occupies roughly 150 to 200 bytes. At 200 bytes per entry, 5 MB holds approximately 25,000 individual rolls. For a play-by-post campaign running three sessions per week with roughly 80 rolls per session, that is over a year of history in a single browser.
IndexedDB
This is a full database living in the browser, with significantly higher storage limits, often measured in hundreds of megabytes or more depending on the browser and available disk space. It is more complex to work with but is the right choice if you are storing detailed roll metadata, character state snapshots, or campaign-level data alongside the roll log.
For securing persistent browser session logs for a dice roller, localStorage is the correct starting point. It is simple, synchronous in its API, universally supported, and more than capacious enough for the use case.
Step 2: Define a Tamper-Resistant Roll Entry Structure
Persistence solves the problem of data surviving. It does not solve the problem of trust. If a player can open their browser's developer tools and edit a stored roll result from a 3 to a 20, persistence has actually made the situation worse, because the fabricated result now looks authoritative.
The first structural decision is to define what a roll entry contains and in what order, so that subsequent integrity checks have a stable input to work with.
A well-structured roll entry for a play-by-post context should include:
- A Unix timestamp in milliseconds - The player's self-identified name (no account required, just a text field) - The dice notation used, such as "2d6+4" - The individual die results, such as [3, 5] - The modifier applied, such as 4 - The final total, such as 12 - A roll purpose or label, such as "Perception check, Session 14"
Storing the individual die results separately from the total is important. It allows anyone reviewing the log to verify the arithmetic themselves. If the stored total does not match the sum of the dice plus the modifier, the log has been altered.
Step 3: Generate a Session Fingerprint
Before any rolls are logged, the browser session needs an identity that is unique and verifiable, even though no user account exists. This is the session fingerprint.
When a player first opens the dice roller, generate a random session identifier using the Web Crypto API. Specifically, use crypto.getRandomValues() to produce a 256-bit random value, then convert it to a hexadecimal string. This becomes the session ID.
Store this session ID in localStorage immediately. On every subsequent visit, the dice roller checks whether a session ID already exists. If it does, it reuses it. If it does not, it generates a new one.
This fingerprint serves two purposes. First, it groups rolls together logically so a Dungeon Master reviewing a log can see which rolls came from the same browser session. Second, it becomes part of the input to the integrity hash described in the next step, which means that altering the session ID to impersonate another player would invalidate every hash that depends on it.
Step 4: Hash Each Roll Entry Using the Web Crypto API
This is the step that transforms a persistent log into a tamper-evident log. Every roll entry, at the moment it is created and before it is stored, is passed through a cryptographic hash function.
The Web Crypto API provides crypto.subtle.digest(), which supports SHA-256. The process works as follows:
Constructing the Hash Input
Concatenate the following fields in a fixed order, separated by a delimiter such as a pipe character: the session ID, the timestamp, the player name, the dice notation, the individual die results as a comma-separated string, the modifier, and the final total.
Computing the Hash
Pass this concatenated string through SHA-256. The output is a 64-character hexadecimal string. Store this hash alongside the roll entry in localStorage.
Why This Matters
If anyone later modifies any field in the stored roll entry, the hash no longer matches. A simple verification function can recompute the hash from the stored fields and compare it to the stored hash. If they differ, the entry has been tampered with.
The key insight is that this verification can be performed by anyone. A Dungeon Master can load the exported log file, run the same verification function, and immediately see which entries are intact and which have been altered. No server is involved. No account is needed. The math does the work.
Step 5: Chain Hashes Together for Full History Integrity
Hashing individual entries detects changes to a single roll. It does not detect deletion. If a player simply removes a poor roll from the log, there is no entry to verify, and the absence is not immediately visible.
The solution is a hash chain, the same structural principle that underpins blockchain ledgers. Each roll entry's hash includes the previous entry's hash as part of its input.
Specifically, the hash input for entry N becomes: the previous entry's hash, followed by the same fields defined in Step 4. The first entry in a session uses a fixed seed value, such as the session ID itself, in place of a previous hash.
This creates a dependency chain. Removing entry N invalidates the hash of entry N+1, which invalidates N+2, and so on through the entire chain. Inserting a fabricated entry has the same cascading effect. The only way to alter the log without detection is to recompute every hash from the point of alteration forward, which requires knowing the exact hash input format.
For a play-by-post Dungeon Master, this means that an exported log file can be verified as a complete, unbroken record. Any gap or alteration surfaces immediately during verification.
Step 6: Handle Storage Limits and Log Rotation Gracefully
Even though 5 MB of localStorage holds roughly 25,000 roll entries, a long-running play-by-post campaign with multiple players could eventually approach this limit, particularly if roll entries include verbose metadata or notes.
The practical approach is to implement a rotation threshold. When the number of stored entries for a given session reaches a defined limit, say 500 entries, the oldest entries are packaged into an export file and then removed from localStorage. The hash chain is re-seeded at the point of truncation, with the last remaining entry's hash becoming the new seed for subsequent entries.
This keeps localStorage usage predictable and bounded while preserving the full history in exported files that the Dungeon Master can archive.
A concrete calculation illustrates the scale. At 500 entries per rotation, with each entry consuming approximately 200 bytes of storage plus its 64-byte hash, a single rotation consumes roughly 132 KB. Even with ten active players each maintaining their own session log in the same browser, the total storage footprint across all sessions stays well under 2 MB, leaving comfortable headroom within the 5 MB localStorage limit.
Step 7: Export and Share Verified Logs Without Accounts
The final step in the workflow is getting the log out of the browser and into the hands of the Dungeon Master or fellow players. This is where the no-account design reaches its full value.
The export function should produce a single JSON file containing:
- The session ID - The session creation timestamp - The complete array of roll entries, each with its individual hash - The final chain hash, which is the hash of the last entry in the log
This file can be shared via any channel the group already uses: a Discord attachment, a forum post, an email. The recipient does not need an account on any platform to read or verify it.
The verification tool should be a standalone HTML page, hosted on the same dice roller site, that accepts the exported JSON file via a file input or drag-and-drop. It reads the file, recomputes every hash in the chain, and reports whether the chain is intact. If any entry fails verification, the tool reports which entry is broken and whether the failure indicates alteration, deletion, or insertion.
This means a Dungeon Master receiving a roll log from a player can verify its integrity in under five seconds, without either party having an account on any service. The trust comes from the hash chain, not from a platform's authentication system.
Step 8: Restore and Continue Logs Across Browser Sessions
When a player returns for the next session, they need to pick up where they left off. The stored session ID in localStorage handles continuity automatically. The dice roller recognizes the existing session, loads the stored roll history, and new rolls are appended to the existing chain.
If a player clears their browser data or switches devices, the localStorage is lost. This is the one genuine limitation of the no-account approach, and it should be stated plainly rather than glossed over. The mitigation is simple: encourage players to export their log at the end of each session and store the file somewhere they control, such as a personal cloud drive or a dedicated folder.
To resume from an export, the dice roller should provide an import function that reads the exported JSON, validates the hash chain, and if the chain is intact, loads the entries into localStorage and continues appending new rolls to the restored chain. The session ID from the export is preserved, maintaining continuity.
This import-verify-restore cycle means that even a complete browser data wipe does not break the chain of trust, as long as the player has the most recent export file. The system degrades gracefully: lose the export and you lose the history, but you never lose the ability to start a fresh, verifiable session.
The Outcome: Account-Free Trust for Play-by-Post Tables
What this architecture delivers is specific and verifiable. A play-by-post group can use a browser-based dice roller where every roll is persisted locally, protected against alteration by a SHA-256 hash chain, exportable as a single portable file, and verifiable by anyone with a browser and the verification tool. No player creates an account. No server stores roll data. No platform holds the trust relationship hostage to a login flow.
The Dungeon Master gets exactly what they need: a log they can verify independently, in seconds, using nothing more than a shared file and a web page. Players get what they need: a dice roller that remembers their history without requiring them to register, remember a password, or trust a third party with their data.
The misconception that no-account means no-persistence is corrected by the storage layer. The misconception that no-account means no-trust is corrected by the hash chain. Both corrections are implementable today, in any modern browser, using APIs that have been stable for years. The work is not in inventing anything new. It is in wiring together what already exists and presenting it as a single, coherent workflow that serves the specific needs of play-by-post tabletop RPG groups.