Skip to content
Blog

Multi-Factor Recovery

All Tribe auth methods are fully pseudonymous — no email or identity is ever stored. If a user loses access to their only auth method, they’re locked out. For password users, there are two built-in recovery paths: password reset (enter email, get a reset link, set a new password) and magic link (enter email, get a login link, signed in directly). Both verify the email against the pseudonymous ID without ever storing it. For wallet and social login users, account linking provides a safety net.

Account linking lets users attach additional auth methods to their account. Each linked method works on its own, so losing one doesn’t mean losing the account.

Account linking and multi-factor authentication sound similar but serve opposite purposes. MFA makes it harder for someone else to break in by requiring multiple factors at sign-in. Account linking makes it harder for the legitimate user to get locked out by giving them multiple independent paths back in.

MFA (e.g. TOTP)Account linking
PurposeHarder to break inNever get locked out
Both required?YesNo, any single linked method is sufficient
Adds friction?YesNo
import { Tribe } from "@tribecloud/sdk";
// See what's already linked
const credentials = await tribe.getLinkedCredentials();
// [{ id, provider: "wallet", walletAddress: "7fXk...", createdAt }]
// Link a Solana wallet (prompts wallet popup)
const updated = await tribe.linkWallet();
// Link Google (shows Google One Tap popup)
const updated = await tribe.linkGoogle();
// Link GitHub, Discord, or Twitter (redirects away and back)
tribe.linkOAuth("github");
const updated = await tribe.unlinkCredential(credentialId);
// Throws if it's the last recovery method — can't lock the user out

When linkOAuth() redirects the user back to your app, the URL includes parameters indicating whether the link succeeded:

const params = new URLSearchParams(window.location.search);
const linked = params.get("tribe_linked"); // "github" | "discord" | etc.
const error = params.get("tribe_link_error"); // "already_linked" | null
if (linked) {
alert(`${linked} linked successfully!`);
params.delete("tribe_linked");
history.replaceState({}, "", `${location.pathname}?${params}`);
}

For users who signed in through a pseudonymous method and haven’t linked any backup, showing a prompt right after their first login is the simplest way to encourage them to protect their account:

function RecoveryPrompt({ credentials }) {
if (credentials.length > 0) return null;
return (
<div role="alert">
<strong>Add a recovery method</strong>
<p>If you lose access to your current login, you won't be able to recover your account.</p>
<button onClick={() => tribe.linkWallet()}>Link a wallet</button>
<button onClick={() => tribe.linkOAuth("github")}>Link GitHub</button>
<button onClick={() => tribe.linkGoogle()}>Link Google</button>
</div>
);
}