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
({
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") {
return <class TextText>You are already signed in</class TextText>; } 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 ( <class ViewView> <class ViewView> <class TextText>Your current passphrase</class TextText> <class TextInputTextInput editable?: boolean | undefined
If false, text is not editable. The default value is true.
editable
={false}
value?: string | undefined
The value to show for the text input. TextInput is a controlled component, which means the native value will be forced to match this value prop if provided. For most uses this works great, but in some cases this may cause flickering - one common cause is preventing edits by keeping value the same. In addition to simply setting the same value, either set editable={false}, or set/update maxLength to prevent unwanted edits without flicker.
value
={
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}
multiline?: boolean | undefined
If true, the text input can be multiple lines. The default value is false.
multiline
numberOfLines?: number | undefined
Sets the number of lines for a TextInput. Use it with multiline set to true to be able to fill the lines.
numberOfLines
={5}
/> </class ViewView> <class ButtonButton title: string
Text to display inside the button. On Android the given title will be converted to the uppercased form.
title
="I have stored my passphrase"
onPress?: ((event: GestureResponderEvent) => void) | undefined
Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).
onPress
={const handleSignUp: () => Promise<void>handleSignUp}
/> <class ViewView> <class TextText>Log in with your passphrase</class TextText> <class TextInputTextInput value?: string | undefined
The value to show for the text input. TextInput is a controlled component, which means the native value will be forced to match this value prop if provided. For most uses this works great, but in some cases this may cause flickering - one common cause is preventing edits by keeping value the same. In addition to simply setting the same value, either set editable={false}, or set/update maxLength to prevent unwanted edits without flicker.
value
={const loginPassphrase: stringloginPassphrase}
onChangeText?: ((text: string) => void) | undefined
Callback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler.
onChangeText
={const setLoginPassphrase: React.Dispatch<React.SetStateAction<string>>setLoginPassphrase}
placeholder?: string | undefined
The string that will be rendered before text input has been entered
placeholder
="Enter your passphrase"
multiline?: boolean | undefined
If true, the text input can be multiple lines. The default value is false.
multiline
numberOfLines?: number | undefined
Sets the number of lines for a TextInput. Use it with multiline set to true to be able to fill the lines.
numberOfLines
={5}
/> </class ViewView> <class ButtonButton title: string
Text to display inside the button. On Android the given title will be converted to the uppercased form.
title
="Log in"
onPress?: ((event: GestureResponderEvent) => void) | undefined
Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).
onPress
={const handleLogIn: () => Promise<void>handleLogIn}
/> </class ViewView> ); }

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