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:

  1. Jazz generates a unique recovery phrase derived from the user's cryptographic keys
  2. This phrase consists of words from a wordlist
  3. 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 function function AuthModal({ open, onOpenChange }: AuthModalProps): React.JSX.ElementAuthModal({ open: booleanopen, onOpenChange: (open: boolean) => voidonOpenChange }: 
type AuthModalProps = {
    open: boolean;
    onOpenChange: (open: boolean) => void;
}
AuthModalProps
) {
const [const loginPassphrase: stringloginPassphrase, 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.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
("");
const
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;
}
auth
=
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.
@example```ts const auth = usePassphraseAuth({ appName, appHostname, wordlist }); ```@categoryAuth Providers
usePassphraseAuth
({ // Must be inside the JazzProvider!
wordlist: string[]wordlist: const wordlist: string[]wordlist, }); if (
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;
}
auth
.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>; } const const handleSignUp: () => Promise<void>handleSignUp = async () => { await
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;
}
auth
.signUp: (name?: string) => Promise<string>signUp();
onOpenChange: (open: boolean) => voidonOpenChange(false); }; const const handleLogIn: () => Promise<void>handleLogIn = async () => { await
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;
}
auth
.logIn: (passphrase: string) => Promise<void>logIn(const loginPassphrase: stringloginPassphrase);
onOpenChange: (open: boolean) => voidonOpenChange(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>textarea React.TextareaHTMLAttributes<HTMLTextAreaElement>.readOnly?: boolean | undefinedreadOnly React.TextareaHTMLAttributes<HTMLTextAreaElement>.value?: string | number | readonly string[] | undefinedvalue={
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;
}
auth
.passphrase: stringpassphrase}
React.TextareaHTMLAttributes<HTMLTextAreaElement>.rows?: number | undefinedrows={5} /> </JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>label> <JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={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>textarea React.TextareaHTMLAttributes<HTMLTextAreaElement>.value?: string | number | readonly string[] | undefinedvalue={const loginPassphrase: stringloginPassphrase} React.TextareaHTMLAttributes<HTMLTextAreaElement>.onChange?: React.ChangeEventHandler<HTMLTextAreaElement> | undefinedonChange={(e: React.ChangeEvent<HTMLTextAreaElement>e) => const setLoginPassphrase: (value: React.SetStateAction<string>) => voidsetLoginPassphrase(e: React.ChangeEvent<HTMLTextAreaElement>e.React.ChangeEvent<HTMLTextAreaElement>.target: EventTarget & HTMLTextAreaElementtarget.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 | undefinedplaceholder="Enter your passphrase" React.TextareaHTMLAttributes<HTMLTextAreaElement>.rows?: number | undefinedrows={5} React.TextareaHTMLAttributes<HTMLTextAreaElement>.required?: boolean | undefinedrequired /> </JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>label> <JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={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:

  1. Store the passphrase in a secure location (password manager, written down in a safe place)
  2. The passphrase is the only way to recover their account
  3. Anyone with the passphrase can access the account