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({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") { return <class Text
Text>You are already signed in</class Text
Text>; } 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 ( <class View
View> <class View
View> <class Text
Text>Your current passphrase</class Text
Text> <class TextInput
TextInputeditable?: 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={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}multiline?: boolean | undefined
If true, the text input can be multiple lines. The default value is false.multilinenumberOfLines?: 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 View
View> <class Button
Buttontitle: 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 View
View> <class Text
Text>Log in with your passphrase</class Text
Text> <class TextInput
TextInputvalue?: 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: string
loginPassphrase}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 enteredplaceholder="Enter your passphrase"multiline?: boolean | undefined
If true, the text input can be multiple lines. The default value is false.multilinenumberOfLines?: 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 View
View> <class Button
Buttontitle: 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 View
View> ); }
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