Session Verification
Sometimes a legitimate user logs in from a new laptop, and sometimes it’s someone who got hold of their credentials. Suspicious login protection helps you tell the difference. When it’s enabled, Tribe flags logins from unrecognized devices for users who have registered passkeys. Rather than blocking the login entirely, Tribe creates the session in an unverified state, and your app decides what to do next. The typical pattern is a verification gate that asks the user to confirm their identity with a passkey before they can proceed.
Enable suspicious login protection
Section titled “Enable suspicious login protection”- Go to the Tribe dashboard
- Open your site’s Settings, Auth, Suspicious Login Protection
- Toggle on Enable suspicious login protection
This feature is opt-in and off by default. It only kicks in when two conditions are both true: the login comes from a device Tribe hasn’t seen before for this user, and the user has at least one registered passkey. If either condition is missing, the session is created as verified and sessionVerified will be true as usual.
How it works
Section titled “How it works”The flow goes like this. A user logs in from a new device using any login method. Tribe recognizes the device is unfamiliar and checks whether the user has passkeys on file. If they do, the session gets created with verified: false, and the login response includes sessionVerified: false. Your app picks this up and shows a verification gate. The user taps their passkey, your code calls tribe.verifySession(), and the session flips to verified. From there, the app continues normally.
Check verification status
Section titled “Check verification status”Every login method and getSession() includes sessionVerified in the response:
// After loginconst { user, sessionVerified } = await tribe.login(email, password);if (sessionVerified === false) { // Show verification gate}
// Or check laterconst session = await tribe.getSession();if (session?.sessionVerified === false) { // Show verification gate}
// Convenience methodconst verified = await tribe.isSessionVerified();Verify the session
Section titled “Verify the session”await tribe.verifySession();// Prompts the browser passkey dialog// On success, the session is marked as verified// Throws if browser doesn't support WebAuthn or user cancelsVerification gate
Section titled “Verification gate”function VerificationGate({ children }) { const [session, setSession] = useState(null); const [loading, setLoading] = useState(true); const [verifying, setVerifying] = useState(false); const [error, setError] = useState("");
useEffect(() => { tribe.getSession().then((s) => { setSession(s); setLoading(false); }); }, []);
if (loading) return <p>Loading...</p>; if (!session) return <p>Not logged in</p>;
if (session.sessionVerified === false) { const handleVerify = async () => { setError(""); setVerifying(true); try { await tribe.verifySession(); setSession(await tribe.getSession()); } catch (err) { if (err.name !== "NotAllowedError") setError(err.message); setVerifying(false); } };
return ( <div> <h2>Verify your identity</h2> <p>We noticed a sign-in from a new device. Verify with your passkey to continue.</p> {error && <p style={{ color: "red" }}>{error}</p>} <button onClick={handleVerify} disabled={verifying}> {verifying ? "Verifying..." : "Verify with passkey"} </button> </div> ); }
return <>{children}</>;}<div id="app" style="display:none"><!-- your app content --></div><div id="verify-gate" style="display:none"> <h2>Verify your identity</h2> <p>We noticed a sign-in from a new device.</p> <button id="verify-btn">Verify with passkey</button></div>
<script src="https://api.tribe.utopian.build/sdk.js?site=YOUR_SITE_ID" defer></script><script defer> window.addEventListener("DOMContentLoaded", async () => { const session = await Tribe.getSession(); if (!session) { window.location.href = "/login"; return; }
if (session.sessionVerified === false) { document.getElementById("verify-gate").style.display = "block"; document.getElementById("verify-btn").addEventListener("click", async () => { try { await Tribe.verifySession(); document.getElementById("verify-gate").style.display = "none"; document.getElementById("app").style.display = "block"; } catch (err) { if (err.name !== "NotAllowedError") alert(err.message); } }); } else { document.getElementById("app").style.display = "block"; } });</script>Check config
Section titled “Check config”You can use getAuthConfig() to find out whether the site has suspicious login protection turned on, and conditionally render verification-related UI based on that:
const config = await tribe.getAuthConfig();if (config.suspiciousLoginProtection) { // Site has this feature enabled — handle sessionVerified accordingly}