nextjs-authentication

Provides authentication implementation patterns for Next.js 15+ App Router using Auth.js 5 (NextAuth.js). Use when setting up authentication flows,…

INSTALLATION
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Next.js Authentication

Overview

Provides authentication implementation patterns for Next.js 15+ App Router using Auth.js 5 (NextAuth.js), covering the complete authentication lifecycle from initial setup to production-ready role-based access control implementations.

When to Use

  • Setting up Auth.js 5 from scratch or adding OAuth providers
  • Implementing protected routes with Middleware
  • Handling authentication in Server Components and Server Actions
  • Implementing role-based access control (RBAC)
  • Creating credential-based or OAuth sign-in/sign-out flows

Instructions

1. Install Dependencies

Install Auth.js v5 (beta) for Next.js App Router:

npm install next-auth@beta

2. Configure Environment Variables

Create .env.local with required variables:

# Required for Auth.js

AUTH_SECRET="your-secret-key-here"

AUTH_URL="http://localhost:3000"

# OAuth Providers (add as needed)

GITHUB_ID="your-github-client-id"

GITHUB_SECRET="your-github-client-secret"

GOOGLE_CLIENT_ID="your-google-client-id"

GOOGLE_CLIENT_SECRET="your-google-client-secret"

Generate AUTH_SECRET with:

openssl rand -base64 32

3. Create Auth Configuration

Create auth.ts in the project root with providers and callbacks:

import NextAuth from "next-auth";

import GitHub from "next-auth/providers/github";

import Google from "next-auth/providers/google";

export const {

  handlers: { GET, POST },

  auth,

  signIn,

  signOut,

} = NextAuth({

  providers: [

    GitHub({

      clientId: process.env.GITHUB_ID!,

      clientSecret: process.env.GITHUB_SECRET!,

    }),

    Google({

      clientId: process.env.GOOGLE_CLIENT_ID!,

      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,

    }),

  ],

  callbacks: {

    async jwt({ token, user }) {

      if (user) {

        token.id = user.id;

      }

      return token;

    },

    async session({ session, token }) {

      if (token) {

        session.user.id = token.id as string;

      }

      return session;

    },

  },

  pages: {

    signIn: "/login",

    error: "/error",

  },

});

4. Create API Route Handler

Create app/api/auth/[...nextauth]/route.ts:

export { GET, POST } from "@/auth";

5. Add Middleware for Route Protection

Create middleware.ts in the project root:

import { auth } from "@/auth";

import { NextResponse } from "next/server";

export default auth((req) => {

  const { nextUrl } = req;

  const isLoggedIn = !!req.auth;

  const isApiAuthRoute = nextUrl.pathname.startsWith("/api/auth");

  const isPublicRoute = ["/", "/login", "/register"].includes(nextUrl.pathname);

  const isProtectedRoute = nextUrl.pathname.startsWith("/dashboard");

  if (isApiAuthRoute) return NextResponse.next();

  if (!isLoggedIn && isProtectedRoute) {

    return NextResponse.redirect(new URL("/login", nextUrl));

  }

  if (isLoggedIn && nextUrl.pathname === "/login") {

    return NextResponse.redirect(new URL("/dashboard", nextUrl));

  }

  return NextResponse.next();

});

export const config = {

  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.png$).*)"],

};

6. Access Session in Server Components

Use the auth() function to access session in Server Components:

import { auth } from "@/auth";

import { redirect } from "next/navigation";

export default async function DashboardPage() {

  const session = await auth();

  if (!session) {

    redirect("/login");

  }

  return (

    <div>

      <h1>Welcome, {session.user.name}</h1>

    </div>

  );

}

7. Secure Server Actions

Always verify authentication in Server Actions before mutations:

"use server";

import { auth } from "@/auth";

export async function createTodo(formData: FormData) {

  const session = await auth();

  if (!session?.user) {

    throw new Error("Unauthorized");

  }

  // Proceed with protected action

  const title = formData.get("title") as string;

  await db.todo.create({

    data: { title, userId: session.user.id },

  });

}

8. Handle Sign-In/Sign-Out

Create a login page with server action:

// app/login/page.tsx

import { signIn } from "@/auth";

import { redirect } from "next/navigation";

export default function LoginPage() {

  async function handleLogin(formData: FormData) {

    "use server";

    const result = await signIn("credentials", {

      email: formData.get("email"),

      password: formData.get("password"),

      redirect: false,

    });

    if (result?.error) {

      return { error: "Invalid credentials" };

    }

    redirect("/dashboard");

  }

  return (

    <form action={handleLogin}>

      <input name="email" type="email" placeholder="Email" required />

      <input name="password" type="password" placeholder="Password" required />

      <button type="submit">Sign In</button>

    </form>

  );

}

For client-side sign-out:

"use client";

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

export function SignOutButton() {

  return <button onClick={() => signOut()}>Sign Out</button>;

}

9. Implement Role-Based Access

Check roles in Server Components:

import { auth } from "@/auth";

import { unauthorized } from "next/navigation";

export default async function AdminPage() {

  const session = await auth();

  if (session?.user?.role !== "admin") {

    unauthorized();

  }

  return <AdminDashboard />;

}

10. Extend TypeScript Types

Create types/next-auth.d.ts for type-safe sessions:

import { DefaultSession } from "next-auth";

declare module "next-auth" {

  interface Session {

    user: {

      id: string;

      role: "user" | "admin";

    } &#x26; DefaultSession["user"];

  }

  interface User {

    role?: "user" | "admin";

  }

}

declare module "next-auth/jwt" {

  interface JWT {

    id?: string;

    role?: "user" | "admin";

  }

}

Examples

Example 1: Complete Protected Dashboard

Input: User needs a dashboard accessible only to authenticated users

Implementation:

// app/dashboard/page.tsx

import { auth } from "@/auth";

import { redirect } from "next/navigation";

import { getUserTodos } from "@/app/lib/data";

export default async function DashboardPage() {

  const session = await auth();

  if (!session?.user?.id) {

    redirect("/login");

  }

  const todos = await getUserTodos(session.user.id);

  return (

    <main>

      <h1>Welcome, {session.user.name}</h1>

      <p>Email: {session.user.email}</p>

      <TodoList todos={todos} />

    </main>

  );

}

Output: Dashboard renders only for authenticated users, with their specific data.

Example 2: Role-Based Admin Panel

Input: Admin panel should be accessible only to users with "admin" role

Implementation:

// app/admin/page.tsx

import { auth } from "@/auth";

import { unauthorized } from "next/navigation";

export default async function AdminPage() {

  const session = await auth();

  if (session?.user?.role !== "admin") {

    unauthorized();

  }

  return (

    <main>

      <h1>Admin Panel</h1>

      <p>Welcome, administrator {session.user.name}</p>

    </main>

  );

}

Output: Only admin users see the panel; others get 401 error.

Example 3: Secure Server Action with Form

Input: Form submission should only work for authenticated users

Implementation:

// app/components/create-todo-form.tsx

"use server";

import { auth } from "@/auth";

import { revalidatePath } from "next/cache";

export async function createTodo(formData: FormData) {

  const session = await auth();

  if (!session?.user?.id) {

    throw new Error("Unauthorized");

  }

  const title = formData.get("title") as string;

  await db.todo.create({

    data: {

      title,

      userId: session.user.id,

    },

  });

  revalidatePath("/dashboard");

}

// Usage in component

export function CreateTodoForm() {

  return (

    <form action={createTodo}>

      <input name="title" placeholder="New todo..." required />

      <button type="submit">Add Todo</button>

    </form>

  );

}

Output: Todo created only for authenticated user; unauthorized requests throw error.

Best Practices

  • Use Server Components by default - Access session directly without client-side JavaScript
  • Minimize Client Components - Only use useSession() for reactive session updates
  • Cache session checks - Use React's cache() for repeated lookups in the same render
  • Middleware for optimistic checks - Redirect quickly, but always re-verify in Server Actions
  • Treat Server Actions like API endpoints - Always authenticate before mutations
  • Never hardcode secrets - Use environment variables for all credentials
  • Implement proper error handling - Return appropriate HTTP status codes
  • Use TypeScript type extensions - Extend NextAuth types for custom fields
  • Separate auth logic - Create a DAL (Data Access Layer) for consistent checks
  • Test authentication flows - Mock auth() function in unit tests

Constraints and Warnings

Critical Limitations

  • Middleware runs on Edge runtime - Cannot use Node.js APIs like database drivers
  • Server Components cannot set cookies - Use Server Actions for cookie operations
  • Session callback timing - Only called on session creation/access, not every request

Common Mistakes

// ❌ WRONG: Setting cookies in Server Component

export default async function Page() {

  cookies().set("key", "value"); // Won't work

}

// ✅ CORRECT: Use Server Action

async function setCookieAction() {

  "use server";

  cookies().set("key", "value");

}
// ❌ WRONG: Database queries in Middleware

export default auth(async (req) => {

  const user = await db.user.findUnique(); // Won't work in Edge

});

// ✅ CORRECT: Use only Edge-compatible APIs

export default auth(async (req) => {

  const session = req.auth; // This works

});

Security Considerations

  • Always verify authentication in Server Actions - middleware alone is not enough
  • Use unauthorized() for unauthenticated access, redirect() for other cases
  • Store sensitive tokens in httpOnly cookies
  • Validate all user input before processing
  • Use HTTPS in production
  • Set appropriate cookie sameSite attributes

References

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card