Passphrase Authentication
Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). Users are responsible for storing this passphrase safely.
How it works
When a user creates an account with passphrase authentication:
- Jazz generates a unique recovery phrase derived from the user's cryptographic keys
- This phrase consists of words from a wordlist
- Users save this phrase and enter it when logging in on new devices
You can use one of the ready-to-use wordlists from the BIP39 repository or create your own.
Key benefits
- Portable: Works across any device, even without browser or OS support
- User-controlled: User manages their authentication phrase
- Flexible: Works with any wordlist you choose
- Offline capable: No external dependencies
Implementation
import {
const wordlist: string[]
wordlist } from "./wordlist" export functionfunction AuthModal({ open, onOpenChange }: AuthModalProps): React.JSX.Element
AuthModal({open: boolean
open,onOpenChange: (open: boolean) => void
onOpenChange }:AuthModalProps) { const [
type AuthModalProps = { open: boolean; onOpenChange: (open: boolean) => void; }
const loginPassphrase: string
loginPassphrase,const setLoginPassphrase: React.Dispatch<React.SetStateAction<string>>
setLoginPassphrase] =useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)
Returns a stateful value, and a function to update it.useState(""); constauth =
const auth: { readonly state: "signedIn" | "anonymous"; readonly logIn: (passphrase: string) => Promise<void>; readonly signUp: (name?: string) => Promise<string>; readonly registerNewAccount: (passphrase: string, name: string) => Promise<import("/vercel/path0/packages/jazz-tools/dist/internal").ID<import("/vercel/path0/packages/jazz-tools/dist/exports").Account>>; readonly generateRandomPassphrase: () => string; readonly passphrase: string; }
function usePassphraseAuth({ wordlist, }: { wordlist: string[]; }): { readonly state: "signedIn" | "anonymous"; readonly logIn: (passphrase: string) => Promise<void>; readonly signUp: (name?: string) => Promise<string>; readonly registerNewAccount: (passphrase: string, name: string) => Promise<import("/vercel/path0/packages/jazz-tools/dist/internal").ID<import("/vercel/path0/packages/jazz-tools/dist/exports").Account>>; readonly generateRandomPassphrase: () => string; readonly passphrase: string; }
`usePassphraseAuth` hook provides a `JazzAuth` object for passphrase authentication.usePassphraseAuth({ // Must be inside the JazzProvider!wordlist: string[]
wordlist:const wordlist: string[]
wordlist, }); if (auth.
const auth: { readonly state: "signedIn" | "anonymous"; readonly logIn: (passphrase: string) => Promise<void>; readonly signUp: (name?: string) => Promise<string>; readonly registerNewAccount: (passphrase: string, name: string) => Promise<import("/vercel/path0/packages/jazz-tools/dist/internal").ID<import("/vercel/path0/packages/jazz-tools/dist/exports").Account>>; readonly generateRandomPassphrase: () => string; readonly passphrase: string; }
state: "signedIn" | "anonymous"
state === "signedIn") { // You can also use `useIsAuthenticated()` return <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>You are already signed in</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>; } constconst handleSignUp: () => Promise<void>
handleSignUp = async () => { awaitauth.
const auth: { readonly state: "signedIn" | "anonymous"; readonly logIn: (passphrase: string) => Promise<void>; readonly signUp: (name?: string) => Promise<string>; readonly registerNewAccount: (passphrase: string, name: string) => Promise<import("/vercel/path0/packages/jazz-tools/dist/internal").ID<import("/vercel/path0/packages/jazz-tools/dist/exports").Account>>; readonly generateRandomPassphrase: () => string; readonly passphrase: string; }
signUp: (name?: string) => Promise<string>
signUp();onOpenChange: (open: boolean) => void
onOpenChange(false); }; constconst handleLogIn: () => Promise<void>
handleLogIn = async () => { awaitauth.
const auth: { readonly state: "signedIn" | "anonymous"; readonly logIn: (passphrase: string) => Promise<void>; readonly signUp: (name?: string) => Promise<string>; readonly registerNewAccount: (passphrase: string, name: string) => Promise<import("/vercel/path0/packages/jazz-tools/dist/internal").ID<import("/vercel/path0/packages/jazz-tools/dist/exports").Account>>; readonly generateRandomPassphrase: () => string; readonly passphrase: string; }
logIn: (passphrase: string) => Promise<void>
logIn(const loginPassphrase: string
loginPassphrase);onOpenChange: (open: boolean) => void
onOpenChange(false); }; return ( <JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> <JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label> Your current passphrase <JSX.IntrinsicElements.textarea: React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>
textareaReact.TextareaHTMLAttributes<HTMLTextAreaElement>.readOnly?: boolean | undefined
readOnlyReact.TextareaHTMLAttributes<HTMLTextAreaElement>.value?: string | number | readonly string[] | undefined
value={auth.
const auth: { readonly state: "signedIn" | "anonymous"; readonly logIn: (passphrase: string) => Promise<void>; readonly signUp: (name?: string) => Promise<string>; readonly registerNewAccount: (passphrase: string, name: string) => Promise<import("/vercel/path0/packages/jazz-tools/dist/internal").ID<import("/vercel/path0/packages/jazz-tools/dist/exports").Account>>; readonly generateRandomPassphrase: () => string; readonly passphrase: string; }
passphrase: string
passphrase}React.TextareaHTMLAttributes<HTMLTextAreaElement>.rows?: number | undefined
rows={5} /> </JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label> <JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
buttonReact.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
onClick={const handleSignUp: () => Promise<void>
handleSignUp}>I have stored my passphrase</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> <JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label> Log in with your passphrase <JSX.IntrinsicElements.textarea: React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>
textareaReact.TextareaHTMLAttributes<HTMLTextAreaElement>.value?: string | number | readonly string[] | undefined
value={const loginPassphrase: string
loginPassphrase}React.TextareaHTMLAttributes<HTMLTextAreaElement>.onChange?: React.ChangeEventHandler<HTMLTextAreaElement> | undefined
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>
e) =>const setLoginPassphrase: (value: React.SetStateAction<string>) => void
setLoginPassphrase(e: React.ChangeEvent<HTMLTextAreaElement>
e.React.ChangeEvent<HTMLTextAreaElement>.target: EventTarget & HTMLTextAreaElement
target.HTMLTextAreaElement.value: string
Retrieves or sets the text in the entry field of the textArea element. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLTextAreaElement/value)value)}React.TextareaHTMLAttributes<HTMLTextAreaElement>.placeholder?: string | undefined
placeholder="Enter your passphrase"React.TextareaHTMLAttributes<HTMLTextAreaElement>.rows?: number | undefined
rows={5}React.TextareaHTMLAttributes<HTMLTextAreaElement>.required?: boolean | undefined
required /> </JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label> <JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
buttonReact.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
onClick={const handleLogIn: () => Promise<void>
handleLogIn}>Log in</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> </JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> ); }
Examples
You can see passphrase authentication in our passphrase example or the todo list demo.
When to use Passphrases
Passphrase authentication is ideal when:
- You need to support older browsers without WebAuthn capabilities
- Your users need to access the app on many different devices
- You want a fallback authentication method alongside passkeys
Limitations and considerations
- User responsibility: Users must securely store their passphrase
- Recovery concerns: If a user loses their passphrase, they cannot recover their account
- Security risk: Anyone with the passphrase can access the account
- User experience: Requires users to enter a potentially long phrase
Make sure to emphasize to your users:
- Store the passphrase in a secure location (password manager, written down in a safe place)
- The passphrase is the only way to recover their account
- Anyone with the passphrase can access the account