Custom Login UI
When your app needs its own branded login experience, you can build custom forms and wire them into the SDK through tribe.login() and tribe.register(). You get complete control over the interface while Tribe handles session management, token storage, and security checks like breached password detection under the hood.
import { Tribe } from "@tribecloud/sdk";import { useState } from "react";
const tribe = new Tribe({ siteId: "YOUR_SITE_ID" });
function LoginForm() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState("");
const handleSubmit = async (e) => { e.preventDefault(); try { const { user } = await tribe.login(email, password); console.log("Logged in:", user); } catch (err) { setError(err.message); } };
return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" /> {error && <p style={{ color: "red" }}>{error}</p>} <button type="submit">Sign In</button> </form> );}<form id="login-form"> <input type="email" id="email" placeholder="Email" /> <input type="password" id="password" placeholder="Password" /> <p id="error" style="color: red; display: none"></p> <button type="submit">Sign In</button></form>
<script src="https://api.tribe.utopian.build/sdk.js?site=YOUR_SITE_ID" defer></script><script defer> document.getElementById("login-form").addEventListener("submit", async (e) => { e.preventDefault(); try { const { user } = await Tribe.login( document.getElementById("email").value, document.getElementById("password").value ); console.log("Logged in:", user); } catch (err) { const errorEl = document.getElementById("error"); errorEl.textContent = err.message; errorEl.style.display = "block"; } });</script>Registration
Section titled “Registration”const { user } = await tribe.register(email, password);Registration behaves just like login from your code’s perspective. The session and user tokens are stored automatically, and the user is signed in the moment their account is created.
Password reset
Section titled “Password reset”If a user forgets their password, they can reset it through a branded email flow. The user provides their email, the system verifies that it computes to the correct pseudonymous ID, and then sends a reset email. The email is never stored on the user record — it’s only used transiently for verification and delivery.
Request a reset email
Section titled “Request a reset email”You can specify a custom URL for the reset page:
resetPasswordUrl: "https://myapp.com/reset-password",});Complete the reset
Section titled “Complete the reset”When the user clicks the link in their email and arrives at your reset page, extract the token from the URL and pass it along with the new password:
const token = new URLSearchParams(window.location.search).get("token");if (token) { const { user } = await tribe.resetPassword(token, newPassword); // User is now logged in with the new password}Email verification
Section titled “Email verification”Authenticated users can verify ownership of their email address. Like password reset, the user provides their email, the system confirms it matches the pseudonymous ID on the account, and sends a branded verification email. The email is never stored.
Request a verification email
Section titled “Request a verification email”// User must be logged inYou can specify a custom verification page URL:
verificationUrl: "https://myapp.com/verify-email",});Verify the token
Section titled “Verify the token”const token = new URLSearchParams(window.location.search).get("token");if (token) { const { user } = await tribe.verifyEmail(token); // Email ownership verified}Checking the session
Section titled “Checking the session”Before rendering a login form, it’s worth checking whether the user already has a valid session. This avoids showing a login page to someone who’s already signed in:
const session = await tribe.getSession();if (session) { // User is already logged in console.log("Welcome back:", session.user);} else { // Show login form}Logging out
Section titled “Logging out”await tribe.logout();// Session token and user token are cleared from localStorageAuth context pattern (React)
Section titled “Auth context pattern (React)”A common pattern in React apps is centralizing auth state in a context provider. This makes the current user and auth actions available anywhere in your component tree without prop drilling:
import { Tribe } from "@tribecloud/sdk";import { createContext, useContext, useEffect, useState } from "react";
const tribe = new Tribe({ siteId: "YOUR_SITE_ID" });const AuthContext = createContext(null);
export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { tribe.getSession().then((session) => { setUser(session?.user ?? null); setLoading(false); }); }, []);
const login = async (email, password) => { const { user } = await tribe.login(email, password); setUser(user); };
const register = async (email, password) => { const { user } = await tribe.register(email, password); setUser(user); };
const logout = async () => { await tribe.logout(); setUser(null); };
return ( <AuthContext.Provider value={{ user, loading, login, register, logout }}> {children} </AuthContext.Provider> );}
export const useAuth = () => { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within AuthProvider"); return ctx;};Access mode
Section titled “Access mode”Sites can operate in either "public" or "internal" access mode. Querying the auth config lets you adapt your UI accordingly, for example hiding the registration form on internal sites:
const config = await tribe.getAuthConfig();if (config.accessMode === "internal") { // Only organization members can register/login // Consider showing "Contact your admin" instead of a register form}