Authentication
Sign-in and sign-out
Hosted sign-in, the embedded form, server-side sign-in and sign-out.
Hosted sign-in#
The recommended path: every method the environment enables, MFA, bot protection and branding, with nothing to build. See Hosted AuthKit for the round trip, and the Next.js quickstart for the code.
Embedded sign-in form#
SignIn from @3een/auth/components renders an email and password form in your own page. It posts to your /api/auth/sign-in route, and asks for a six-digit authenticator code if the account has MFA. The pending MFA state is kept in a short-lived httpOnly cookie.
'use client';
// app/sign-in/page.tsx — the embedded form (password + TOTP)
import { SignIn } from '@3een/auth/components';
export default function SignInPage() {
// Posts to your own /api/auth/sign-in; the API key never reaches the
// browser. Passkeys, magic codes, social and SSO stay on the hosted page,
// linked under the form.
return <SignIn afterSignInUrl="/dashboard" labels={{ title: 'Sign in to Acme' }} />;
}Server-side sign-in#
AuthClient.signIn returns the tokens and the user, for flows you drive yourself. Keep the tokens in httpOnly cookies. A wrong password and an unknown address get the same answer: 401 with code invalid_credentials.
Sign-out#
signOut() from @3een/auth/components posts to your /api/auth/sign-out, which revokes the session with the API, clears the cookies and then navigates to the return path. On Express, mount handleSignOut(config).
'use client';
// Hide or show UI by role or permission. This does not protect data: the
// route behind it must check too (getAuth().has() or requirePermission).
import { OrganizationSwitcher, Protect, signOut, useSession } from '@3een/auth/components';
export function BillingMenu() {
const { user, loading, has } = useSession();
if (loading) return <p>Loading…</p>;
if (!user) return null;
return (
<nav aria-label="Account">
<OrganizationSwitcher onSwitched={(organizationId) => console.info('Now in', organizationId)} />
<Protect permission="billing:read" fallback={<p>Ask an admin for billing access.</p>}>
<a href="/billing">Billing</a>
</Protect>
{has({ role: 'admin' }) && <a href="/admin">Admin</a>}
<button type="button" onClick={() => signOut('/api/auth', '/')}>
Sign out
</button>
</nav>
);
}