Skip to content
Blog

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.

  1. Go to the Tribe dashboard
  2. Open your site’s Settings, Auth, Passkey Login
  3. Toggle on Enable passkey login

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 WebAuthn
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 triggered

A 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>
);
}

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 user
await tribe.registerPasskey("My laptop");
// deviceName is optional — helps users tell their passkeys apart
// List all registered passkeys
const passkeys = await tribe.getPasskeys();
// [{ id, deviceName, transports, createdAt }]
// Remove a passkey
await 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>
);
}

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.