HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
7 Topics
Chapter•25 Minutes

6. Hooks, Sign In, Sign Out, and UI

Use SessionProvider and useSession, build Google and Credentials buttons, and implement a custom sign-in page.

Wrap the Pages Router with SessionProvider

In pages/_app.js, wrap the application with SessionProvider. This enables client components to call useSession(). It is a UI session provider, not a server-side security boundary.

File: `pages/_app.js`
import { SessionProvider } from "next-auth/react";

export default function App({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}
💡 Pass an initial session through page props where you already load it server-side.

Read Session with `useSession()`

useSession() returns { data, status }. Status is typically loading, authenticated, or unauthenticated. Handle the loading state to avoid flickering the wrong UI. Use this hook in components under SessionProvider; use getServerSession in server-side Pages Router code.

Session status component
import { useSession } from "next-auth/react";

export default function AuthStatus() {
  const { data: session, status } = useSession();
  if (status === "loading") return <p>Checking session…</p>;
  if (status === "unauthenticated") return <p>You are signed out.</p>;
  return <p>Signed in as {session.user?.email}</p>;
}
💡 Only render fields intentionally exposed by the session callback.

Sign In with Google

Call signIn("google", { callbackUrl: "/dashboard" }) from next-auth/react. NextAuth performs the redirect and callback exchange.

Google button
import { signIn } from "next-auth/react";

export default function GoogleSignInButton() {
  return <button onClick={() => signIn("google", { callbackUrl: "/dashboard" })}>Continue with Google</button>;
}
💡 Ensure /dashboard is an allowed destination.

Sign In with Credentials

The field names passed to signIn("credentials", ...) must match the configured provider fields. With redirect: false, handle the returned result and show a generic error. The actual password verification occurs server-side in authorize.

Credentials form
import { useState } from "react";
import { signIn } from "next-auth/react";

export default function CredentialsForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const [busy, setBusy] = useState(false);

  async function submit(event) {
    event.preventDefault();
    setError("");
    setBusy(true);
    try {
      const result = await signIn("credentials", {
        email, password, redirect: false, callbackUrl: "/dashboard",
      });
      if (result?.error) setError("Invalid email or password.");
      else if (result?.url) window.location.assign(result.url);
    } catch {
      setError("Unable to sign in. Please try again.");
    } finally {
      setBusy(false);
    }
  }

  return <form onSubmit={submit}>
    <label>Email <input type="email" autoComplete="email" required value={email} onChange={e => setEmail(e.target.value)} /></label>
    <label>Password <input type="password" autoComplete="current-password" required value={password} onChange={e => setPassword(e.target.value)} /></label>
    <button disabled={busy}>{busy ? "Signing in…" : "Sign in"}</button>
    {error && <p role="alert">{error}</p>}
  </form>;
}
💡 Do not log the form values. This example uses a generic error message and a loading state.

Sign Out

Call signOut() from next-auth/react. In v4, callbackUrl sets the destination. If your backend also maintains its own session or refresh token, revoke it through a deliberate backend logout flow too.

Sign-out button
import { signOut } from "next-auth/react";

export default function SignOutButton() {
  return <button onClick={() => signOut({ callbackUrl: "/" })}>Sign out</button>;
}
💡 Do not implement logout by only hiding UI or manually deleting arbitrary cookies.

Create a Custom Sign-In Page

Create pages/auth/signin.js, place the Google button and credentials form there, and configure pages: { signIn: "/auth/signin" } in authOptions. Ensure the sign-in page is public to avoid redirect loops.

File: `pages/auth/signin.js`
import GoogleSignInButton from "../../components/GoogleSignInButton";
import CredentialsForm from "../../components/CredentialsForm";

export default function SignInPage() {
  return <main><h1>Sign in</h1><GoogleSignInButton /><hr /><CredentialsForm /></main>;
}
💡 Adjust import paths to match your file structure.

Update and Refetch Session

useSession() provides an update() method in supported v4 releases. Calling it refreshes session data; passing client data may trigger the update path. Treat all browser-supplied values as untrusted and validate them on the server. Avoid frequent polling unless your product needs it.

Refresh session
import { useSession } from "next-auth/react";

export default function RefreshSessionButton() {
  const { update } = useSession();
  return <button onClick={() => update()}>Refresh session</button>;
}
💡 Never accept a client-provided role or permission change without checking a trusted source.