Passkey Login
Passkeys replace passwords with something users already have: their fingerprint, their face, or a hardware security key. Under the hood, the browser handles a WebAuthn challenge-response flow, which means the cryptographic secret never leaves the device and can’t be phished. For your users, it just feels like tapping a button and glancing at their phone.
Enable passkey login
Section titled “Enable passkey login”- Go to the Tribe dashboard
- Open your site’s Settings, Auth, Passkey Login
- Toggle on Enable passkey login
Check browser support
Section titled “Check browser support”Not every browser supports WebAuthn yet, so it’s worth checking before you render a passkey button. Showing a login option that silently fails is worse than not showing it at all.
import { Tribe } from "@tribecloud/sdk";
const available = tribe.isPasskeyAvailable();// Returns true if the browser supports WebAuthnSign in with a passkey
Section titled “Sign in with a passkey”const { user, sessionVerified } = await tribe.loginWithPasskey();// user.email or user.pseudonymousId — depends on how the account was created// user.authMethod — "passkey"// sessionVerified — false if suspicious login protection triggeredA single call handles the entire WebAuthn sequence. The SDK requests authentication options from the Tribe server, prompts the browser’s passkey dialog (fingerprint, face scan, or security key tap), sends the signed credential back for verification, and stores the resulting session token. Your code just awaits the promise.
function PasskeyLoginButton() { const [loading, setLoading] = useState(false);
if (!tribe.isPasskeyAvailable()) return null;
const handleClick = async () => { setLoading(true); try { const { user } = await tribe.loginWithPasskey(); console.log("Logged in:", user.email ?? user.pseudonymousId); } catch (err) { if (err.name !== "NotAllowedError") { console.error("Passkey login failed:", err); } } finally { setLoading(false); } };
return ( <button onClick={handleClick} disabled={loading}> {loading ? "Authenticating..." : "Sign in with passkey"} </button> );}<button id="passkey-btn" style="display:none">Sign in with passkey</button>
<script src="https://api.tribe.utopian.build/sdk.js?site=YOUR_SITE_ID" defer></script><script defer> window.addEventListener("DOMContentLoaded", () => { if (!Tribe.isPasskeyAvailable()) return;
const btn = document.getElementById("passkey-btn"); btn.style.display = "block"; btn.addEventListener("click", async () => { try { const { user } = await Tribe.loginWithPasskey(); console.log("Logged in:", user.email ?? user.pseudonymousId); } catch (err) { if (err.name !== "NotAllowedError") { console.error("Passkey login failed:", err); } } }); });</script>Register a passkey
Section titled “Register a passkey”A user needs to be logged in before they can register a passkey, since the passkey gets tied to their existing account. Once registered, it becomes another way to sign in next time.
// Register a passkey for the current userawait tribe.registerPasskey("My laptop");// deviceName is optional — helps users tell their passkeys apartManage passkeys
Section titled “Manage passkeys”// List all registered passkeysconst passkeys = await tribe.getPasskeys();// [{ id, deviceName, transports, createdAt }]
// Remove a passkeyawait tribe.deletePasskey(passkeyId);function PasskeyManager() { const [passkeys, setPasskeys] = useState([]); const [loading, setLoading] = useState(false);
useEffect(() => { tribe.getPasskeys().then(setPasskeys); }, []);
const handleAdd = async () => { setLoading(true); try { await tribe.registerPasskey(); setPasskeys(await tribe.getPasskeys()); } catch (err) { if (err.name !== "NotAllowedError") console.error(err); } finally { setLoading(false); } };
const handleDelete = async (id) => { await tribe.deletePasskey(id); setPasskeys(await tribe.getPasskeys()); };
return ( <div> <h3>Your Passkeys</h3> {passkeys.map((pk) => ( <div key={pk.id}> <span>{pk.deviceName ?? "Passkey"}</span> <button onClick={() => handleDelete(pk.id)}>Remove</button> </div> ))} <button onClick={handleAdd} disabled={loading}> {loading ? "Adding..." : "Add passkey"} </button> </div> );}Domain binding
Section titled “Domain binding”Passkeys are bound to the domain where they were registered (the WebAuthn Relying Party ID). A passkey created on myapp.com will only work on myapp.com, not on Tribe’s hosted auth pages at api.tribe.utopian.build. This is the same constraint that applies to wallet login: passkey authentication only works through the SDK running on your own domain.