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.
import { SessionProvider } from "next-auth/react";
export default function App({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}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.
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>;
}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.
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>;
}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.
import { signOut } from "next-auth/react";
export default function SignOutButton() {
return <button onClick={() => signOut({ callbackUrl: "/" })}>Sign out</button>;
}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.
import GoogleSignInButton from "../../components/GoogleSignInButton";
import CredentialsForm from "../../components/CredentialsForm";
export default function SignInPage() {
return <main><h1>Sign in</h1><GoogleSignInButton /><hr /><CredentialsForm /></main>;
}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.
import { useSession } from "next-auth/react";
export default function RefreshSessionButton() {
const { update } = useSession();
return <button onClick={() => update()}>Refresh session</button>;
}