TausiDocs

Next.js

App Router (Next.js 15 and 16). The session lives in an HttpOnly cookie on your domain: browser JavaScript never sees the token, server components read it, and a proxy route forwards calls to Tausi.

Install

@tausi-auth/js on npmRequires React 18+ for the React helpers, Next.js 15+ for the Next.js helpers
Shell
npm install @tausi-auth/js
.env.local.env
TAUSI_URL=https://tausi-auth.onrender.com
TAUSI_PUBLISHABLE_KEY=pk_…
TAUSI_SECRET_KEY=sk_…   # no NEXT_PUBLIC_ prefix: it must stay on the server

Add your app's origin (for example http://localhost:3000) to the project's allowed origins.

Server setup

One server-only module configures everything:

lib/tausi.tsTypeScript
import "server-only";
import { createTausiNext } from "@tausi-auth/js/next";

export const tausi = createTausiNext({
  baseUrl: process.env.TAUSI_URL!,
  publishableKey: process.env.TAUSI_PUBLISHABLE_KEY!,
  secretKey: process.env.TAUSI_SECRET_KEY!,
  signInUrl: "/sign-in",
});

The proxy route holds the session cookie. The browser calls /api/tausi/*; the route adds the session and forwards to Tausi, and strips tokens from the responses:

app/api/tausi/[...path]/route.tsTypeScript
import { tausi } from "@/lib/tausi";

export const { GET, POST, PATCH, DELETE } = tausi.proxyHandlers;

Client provider

app/providers.tsxTSX
"use client";
import { TausiNextProvider } from "@tausi-auth/js/next/client";

export function Providers({ baseUrl, publishableKey, children }: { baseUrl: string; publishableKey: string; children: React.ReactNode }) {
  return (
    <TausiNextProvider baseUrl={baseUrl} publishableKey={publishableKey}>
      {children}
    </TausiNextProvider>
  );
}
app/layout.tsxTSX
import { Providers } from "./providers";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers baseUrl={process.env.TAUSI_URL!} publishableKey={process.env.TAUSI_PUBLISHABLE_KEY!}>
          {children}
        </Providers>
      </body>
    </html>
  );
}

The provider refreshes server components by itself when someone signs in, signs out or switches account. In client components, use useAuth() (status, user, isSignedIn, signOut), useUser(), <SignedIn>, <SignedOut> and useTausi() for the client itself.

Sign-in and sign-up

app/sign-in/page.tsxTSX
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useTausi } from "@tausi-auth/js/next/client";
import { isTausiError } from "@tausi-auth/js";

export default function SignIn() {
  const tausi = useTausi();
  const router = useRouter();
  const [error, setError] = useState<string | null>(null);

  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const f = new FormData(e.currentTarget);
    try {
      await tausi.signIn({ email: String(f.get("email")), password: String(f.get("password")) });
      router.push(new URLSearchParams(location.search).get("redirect_url") ?? "/dashboard");
    } catch (err) {
      if (!isTausiError(err)) throw err;
      setError(
        err.code === "INVALID_CREDENTIALS" ? "Wrong email or password."
        : err.code === "EMAIL_NOT_VERIFIED" ? "Check your inbox to verify your email first."
        : err.message, // ACCOUNT_BANNED, ACCOUNT_SUSPENDED, RATE_LIMITED, ...
      );
    }
  }

  return (
    <form method="post" onSubmit={onSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button>Sign in</button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}
Always use method="post"
If the page's JavaScript hasn't loaded yet, a form without method="post" falls back to a GET submit and puts the password in the URL. In development, open the app at localhost; for other hosts (127.0.0.1, your LAN IP) add them to allowedDevOrigins in next.config.ts, or the page never becomes interactive.

Sign-up accepts optional profile attributes. If the email already has an account you get EMAIL_ALREADY_REGISTERED, and Tausi emails the owner:

sign-upTypeScript
try {
  const { verificationRequired } = await tausi.signUp({
    name, email, password,
    username, phoneNumber,                                   // optional
    locale: navigator.language,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    redirectTo: `${location.origin}/welcome`,              // where the verification link opens (optional)
  });
  // verificationRequired: tell them to check their inbox
} catch (err) {
  if (isTausiError(err) && err.code === "EMAIL_ALREADY_REGISTERED") {
    // offer "Sign in" and "Reset password"
  }
}

Protect pages and APIs

Server components and route handlers ask Tausi once per request, so a revoked session or a ban takes effect immediately:

app/dashboard/page.tsxTSX
import { tausi } from "@/lib/tausi";

export default async function Dashboard() {
  const { user } = await tausi.requireAuth({ returnTo: "/dashboard" }); // redirects to /sign-in when signed out
  return <h1>Hello {user.firstName ?? user.name}</h1>;
}
app/api/orders/route.tsTypeScript
import { tausi } from "@/lib/tausi";

// Works with the session cookie (your pages) and with Authorization: Bearer (mobile apps).
export async function GET() {
  const session = await tausi.auth();
  if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 });
  return Response.json(await ordersFor(session.user.id));
}

For a fast redirect before rendering, add the proxy guard. It only checks that the cookie exists (no network call), so pages must still call auth() or requireAuth():

proxy.tsTypeScript
import { tausi } from "@/lib/tausi";

export const proxy = tausi.proxy({ protect: ["/dashboard", "/settings"] });
export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico|api/tausi).*)"] };
HelperReturns
tausi.auth(){ user, session } or null. Throws if Tausi can't be reached (your error.tsx handles it).
tausi.currentUser()The user or null.
tausi.requireAuth({ returnTo }){ user, session }, or redirects to signInUrl?redirect_url=…
tausi.proxy({ protect })A proxy.ts function that redirects visitors without a session cookie.

Social sign-in

Enable providers in the project's Social sign-in tab, then:

sign-in buttonsTSX
const methods = await tausi.getProviders(); // [{ id: "google", kind: "oauth", displayName: "Google" }, ...]

<button onClick={() => tausi.signInWithOAuth("google", { redirectTo: `${location.origin}/auth/callback` })}>
  Continue with Google
</button>
app/auth/callback/page.tsxTSX
"use client";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useOAuthCallback } from "@tausi-auth/js/next/client";

export default function Callback() {
  const { status, error } = useOAuthCallback();   // exchanges the one-time code (PKCE) through the proxy
  const router = useRouter();
  useEffect(() => { if (status === "done") router.replace("/dashboard"); }, [status, router]);
  if (status === "error") return <p>{error?.message}</p>;   // e.g. account_banned, access_denied
  return <p>Signing you in…</p>;
}

redirectTo must be on one of the project's allowed origins.

Email links

Tausi emails link to your App URL's /verify-email and /reset-password, or to the redirectTo your app passed (how it works). Read the token from the URL and redeem it on a click, not on page load, so email link scanners can't use it up:

app/verify-email/page.tsxTSX
"use client";
import { useTausi } from "@tausi-auth/js/next/client";

export default function VerifyEmail() {
  const tausi = useTausi();
  const verify = () => tausi.verifyEmail(new URLSearchParams(location.search).get("token") ?? "");
  return <button onClick={verify}>Verify my email</button>;
}

// app/reset-password/page.tsx: same idea with
//   tausi.resetPassword(token, newPassword)
// and a "forgot password" form that calls
//   tausi.forgotPassword(email, { redirectTo: `${location.origin}/reset-password` })

Profile and sessions

client componentTypeScript
const tausi = useTausi();

await tausi.updateUser({ firstName: "Asha", username: "asha", timezone: null }); // null clears a field
await tausi.changePassword(current, next);      // signs out other devices
const sessions = await tausi.listSessions();     // each with userAgent, ipAddress, lastActiveAt, current
await tausi.revokeSession(sessions[1].id);
await tausi.revokeOtherSessions();
await tausi.signOut();

Manage users from Next.js

For admin pages, use the backend client from @tausi-auth/node in server code (it uses the secret key). See Managing users for every operation.

lib/tausi-admin.tsTypeScript
import "server-only";
import { TausiBackend } from "@tausi-auth/node";

export const admin = new TausiBackend({ baseUrl: process.env.TAUSI_URL!, secretKey: process.env.TAUSI_SECRET_KEY! });

// const { items } = await admin.users.list({ access: "banned" });
// await admin.users.suspend(userId, { durationHours: 24, reason: "Chargeback" });

Token mode (no cookie)

If you'd rather keep the token in the browser (for example to call another API directly), use token mode. The token lives in localStorage, where any script on the page can read it, so prefer cookie mode when you can.

  1. Pass mode="token" and sessionSyncUrl="/api/tausi/session" to TausiNextProvider.
  2. Create app/api/tausi/session/route.ts with export const { POST, DELETE } = tausi.handlers; so server components still see the session.
  3. Call other APIs with Authorization: Bearer ${await tausi.getToken()}.