Recovery Phrases
A key's seed is whatever your app chooses. If your users want stronger protection, you can make it a recovery phrase: 12 random words, like the ones crypto wallets use. A phrase like that can't be guessed.
This is optional. It's a separate small add-on, and the word list it needs is downloaded only when a phrase is made or checked, so apps that don't use it load nothing extra.
What a recovery phrase is, and isn't
- Anyone who has the phrase gets in. Treat it like a password your users can't reset.
- A lost phrase can't be recovered. Nobody, including PrefsDB, can look it up.
- It protects access, not the data itself. Unlike a crypto wallet's phrase, it doesn't encrypt anything. To keep data unreadable on the server, add client-side encryption.
How it works
- At sign-up, your page makes a phrase in the browser. The words are picked there, at random, from a list of 7,776 words, so the phrase never reaches PrefsDB.
- Your page shows the phrase and asks the user to write it down.
- The phrase is the seed: your page passes it to
createkey()orgetkey()like any other seed. - On a new device, the user types the phrase back in. Your page checks it for typos first, then calls
getkey().
Add it to your page
<div id="phrase"></div>
<script src="https://prefsdb.com/prefsdb.v1.min.js"></script>
<script src="https://prefsdb.com/prefsdb.phrase.v1.min.js"></script>
<script>
async function signUp(email) {
// Make a phrase and show it
const phrase = await PrefsDB.generatePhrase(); // 12 words
PrefsDB.showPhrase("#phrase", phrase, {
onConfirm: async () => {
// The user ticked "I've written down all 12 words" and pressed Continue
const db = await PrefsDB.createkey("settings", phrase, { email });
await db.key("theme").write("dark");
},
});
}
</script>
Later, on a new device:
const typed = PrefsDB.normalizePhrase(input.value); // case and spacing don't matter
const check = await PrefsDB.checkPhrase(typed);
if (!check.valid) {
showError(check.unknown.length
? "Check these words: " + check.unknown.join(", ")
: "A recovery phrase has 12 words.");
} else {
const db = await PrefsDB.getkey("settings", typed);
}
Always check the phrase before calling getkey(). One wrong word makes it a different seed. Without an email getkey() would answer "no such key"; with one it would create a new, empty key instead of finding the user's data.
Options
| Call | What it does |
|---|---|
PrefsDB.generatePhrase({ words }) |
A new phrase. words is 12 by default, from 6 to 24. Every word adds about 13 bits; 12 words is already far beyond guessing. |
PrefsDB.showPhrase(element, phrase, options) |
Shows the numbered words, a "written down" checkbox and a Continue button. Options: onConfirm, copy: true (adds a Copy button; off by default, since clipboard history and synced clipboards keep what they're given), columns (default 3), labels (your own wording). Returns an object with destroy(). |
PrefsDB.normalizePhrase(text) |
The phrase as the seed must be: lowercase, one space between words. Use it on anything the user types. |
PrefsDB.checkPhrase(text, { words }) |
{ valid, words, unknown }: whether every word is on the list and the count is right, and which words aren't on it. |
The elements showPhrase() adds have classes starting with prefsdb-phrase- for your own styling.
If something goes wrong
error |
What it means |
|---|---|
PHRASE_LIST_UNAVAILABLE |
The word list couldn't be downloaded (offline, or blocked by your site's Content-Security-Policy, which must allow https://prefsdb.com under connect-src). Trying again later works. |
PHRASE_INVALID_OPTIONS |
A word count outside 6–24, or showPhrase() couldn't find its element. |
Good to know
- Use the same
nameevery time. A key is the name plus the phrase, as with any seed. - Let users see their data with the review link,
db.reviewUrl(): it asks for the name and the phrase. - The word list is the Electronic Frontier Foundation's large word list, used under CC BY 3.0 US, with its dice numbers removed. Its words were chosen to be easy to read, spell and type.