Skip to main content
Technology & EngineeringAuth Services321 lines

Nextauth

Build with NextAuth.js (Auth.js) for authentication in Next.js and other frameworks. Use this skill when the project needs self-hosted auth with OAuth providers, credentials, magic links, database sessions, JWT, callbacks, or middleware protection. Covers Auth.js v5, providers, adapters, callbacks, middleware, and session management.

Quick Summary34 lines
You are an auth specialist who integrates NextAuth.js (Auth.js v5) into projects.
Auth.js is an open-source, self-hosted authentication library for Next.js and other
frameworks, supporting OAuth, credentials, magic links, and database-backed sessions.

## Key Points

- Use JWT strategy for serverless (no database session lookups)
- Use database strategy when you need to revoke sessions instantly
- Extend the session via callbacks to include user ID and roles
- Use middleware for route protection — cleaner than checking in every page
- Always set `AUTH_SECRET` — required for token encryption
- Use the Credentials provider only with proper password hashing (bcrypt/argon2)
- Type-augment the Session and JWT interfaces for type safety
- Storing sensitive data in the JWT — it's readable (base64), not encrypted
- Using Credentials provider without rate limiting — vulnerable to brute force
- Not setting custom pages — the default pages are functional but unstyled
- Calling `useSession()` without `<SessionProvider>` — returns undefined
- Not handling the loading state — `status` starts as `"loading"`

## Quick Example

```bash
npm install next-auth@beta
```

```env
AUTH_SECRET=<random-32-byte-string>
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
```
skilldb get auth-services-skills/nextauthFull skill: 321 lines
Paste into your CLAUDE.md or agent config

NextAuth.js (Auth.js) Integration

You are an auth specialist who integrates NextAuth.js (Auth.js v5) into projects. Auth.js is an open-source, self-hosted authentication library for Next.js and other frameworks, supporting OAuth, credentials, magic links, and database-backed sessions.

Core Philosophy

Self-hosted, zero vendor lock-in

Auth.js runs in your application — no external auth service, no per-user pricing. You own the user data, the session storage, and the auth flow. Switch providers or databases without changing your auth code.

Provider-agnostic

Auth.js supports 80+ OAuth providers (Google, GitHub, Discord, etc.), email/magic link, and custom credentials. Adding a provider is a few lines of config. Removing one is deleting those lines.

Adapter pattern

Auth.js uses adapters to persist users and sessions to any database — Prisma, Drizzle, MongoDB, Supabase, DynamoDB. The adapter interface is simple: implement a few functions and your database is wired in.

Setup

Install (Auth.js v5)

npm install next-auth@beta

Environment variables

AUTH_SECRET=<random-32-byte-string>
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret

Configuration

// auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from './lib/prisma';
import bcrypt from 'bcryptjs';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    Google,
    GitHub,
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      authorize: async (credentials) => {
        const user = await prisma.user.findUnique({
          where: { email: credentials.email as string },
        });
        if (!user?.hashedPassword) return null;

        const valid = await bcrypt.compare(
          credentials.password as string,
          user.hashedPassword
        );
        return valid ? user : null;
      },
    }),
  ],
  session: { strategy: 'jwt' },
  pages: {
    signIn: '/login',
    error: '/login',
  },
});

Route handler

// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';

export const { GET, POST } = handlers;

Middleware

// middleware.ts
import { auth } from './auth';

export default auth((req) => {
  if (!req.auth && req.nextUrl.pathname !== '/login') {
    const newUrl = new URL('/login', req.nextUrl.origin);
    return Response.redirect(newUrl);
  }
});

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

Key Techniques

Server-side session

import { auth } from '@/auth';

// In Server Components
export default async function DashboardPage() {
  const session = await auth();
  if (!session?.user) redirect('/login');

  return <div>Welcome {session.user.name}</div>;
}

// In API routes
export async function GET() {
  const session = await auth();
  if (!session?.user) {
    return new Response('Unauthorized', { status: 401 });
  }

  const data = await prisma.post.findMany({
    where: { authorId: session.user.id },
  });

  return Response.json(data);
}

Client-side session

'use client';
import { useSession, signIn, signOut } from 'next-auth/react';

function AuthButton() {
  const { data: session, status } = useSession();

  if (status === 'loading') return <div>Loading...</div>;

  if (session) {
    return (
      <div>
        <p>{session.user?.email}</p>
        <button onClick={() => signOut()}>Sign out</button>
      </div>
    );
  }

  return (
    <div>
      <button onClick={() => signIn('google')}>Sign in with Google</button>
      <button onClick={() => signIn('github')}>Sign in with GitHub</button>
    </div>
  );
}

Session provider (layout)

// app/layout.tsx
import { SessionProvider } from 'next-auth/react';
import { auth } from '@/auth';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const session = await auth();

  return (
    <html>
      <body>
        <SessionProvider session={session}>
          {children}
        </SessionProvider>
      </body>
    </html>
  );
}

Callbacks (customize tokens and sessions)

export const { handlers, auth, signIn, signOut } = NextAuth({
  // ...providers, adapter
  callbacks: {
    // Add user ID and role to JWT
    jwt({ token, user }) {
      if (user) {
        token.id = user.id;
        token.role = user.role;
      }
      return token;
    },
    // Add user ID and role to session
    session({ session, token }) {
      session.user.id = token.id as string;
      session.user.role = token.role as string;
      return session;
    },
    // Control who can sign in
    signIn({ user, account }) {
      if (account?.provider === 'credentials' && !user) return false;
      return true;
    },
    // Control redirects
    redirect({ url, baseUrl }) {
      if (url.startsWith(baseUrl)) return url;
      return baseUrl + '/dashboard';
    },
  },
});

Type augmentation

// types/next-auth.d.ts
import type { DefaultSession } from 'next-auth';

declare module 'next-auth' {
  interface Session {
    user: {
      id: string;
      role: string;
    } & DefaultSession['user'];
  }

  interface User {
    role: string;
  }
}

declare module 'next-auth/jwt' {
  interface JWT {
    id: string;
    role: string;
  }
}

Email/magic link provider

import Resend from 'next-auth/providers/resend';

export const { handlers, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    Resend({
      from: 'noreply@yourdomain.com',
    }),
  ],
});

Database adapters

// Prisma
import { PrismaAdapter } from '@auth/prisma-adapter';
adapter: PrismaAdapter(prisma)

// Drizzle
import { DrizzleAdapter } from '@auth/drizzle-adapter';
adapter: DrizzleAdapter(db)

// Supabase
import { SupabaseAdapter } from '@auth/supabase-adapter';
adapter: SupabaseAdapter({ url, secret })

// MongoDB
import { MongoDBAdapter } from '@auth/mongodb-adapter';
adapter: MongoDBAdapter(clientPromise)

Server Actions

import { signIn, signOut } from '@/auth';

// Sign in form
export default function LoginPage() {
  return (
    <form action={async () => {
      'use server';
      await signIn('google', { redirectTo: '/dashboard' });
    }}>
      <button type="submit">Sign in with Google</button>
    </form>
  );
}

Best Practices

  • Use JWT strategy for serverless (no database session lookups)
  • Use database strategy when you need to revoke sessions instantly
  • Extend the session via callbacks to include user ID and roles
  • Use middleware for route protection — cleaner than checking in every page
  • Always set AUTH_SECRET — required for token encryption
  • Use the Credentials provider only with proper password hashing (bcrypt/argon2)
  • Type-augment the Session and JWT interfaces for type safety

Anti-Patterns

  • Storing sensitive data in the JWT — it's readable (base64), not encrypted
  • Using Credentials provider without rate limiting — vulnerable to brute force
  • Not setting custom pages — the default pages are functional but unstyled
  • Calling useSession() without <SessionProvider> — returns undefined
  • Not handling the loading state — status starts as "loading"
  • Mixing database and JWT strategies without understanding the trade-offs

Install this skill directly: skilldb add auth-services-skills

Get CLI access →

Related Skills

Ory Kratos

Integrate Ory Kratos as a self-sovereign, API-first identity and user management system. Use this skill when you need a robust, scalable, and secure solution for user registration, login, profile management, and session handling in modern web applications, SPAs, or microservices, without building core identity flows from scratch.

Auth Services333L

Passage

Integrate Passage as a complete passwordless authentication and user management service. Use this skill to implement secure, frictionless login experiences using FIDO2/WebAuthn passkeys, magic links, and one-time passcodes, eliminating traditional passwords from your web and mobile applications.

Auth Services254L

Stytch

Build with Stytch for passwordless and modern authentication. Use this skill when the project needs to integrate Stytch for magic links, OTPs, OAuth, passkeys, biometrics, session management, or B2B organization auth. Covers the Stytch Node SDK, passwordless flows, session management, and B2B multi-tenancy.

Auth Services213L

Supabase Auth

Integrate Supabase Auth for user authentication and authorization in web applications. Use this skill when you need a full-featured, secure, and easy-to-use authentication solution with OAuth, email/password, magic links, and robust session management, tightly integrated with a PostgreSQL database and other Supabase services.

Auth Services287L

Workos

Build with WorkOS for enterprise SSO and authentication. Use this skill when the project needs to integrate WorkOS for SAML/OIDC single sign-on, directory sync (SCIM), admin portal, AuthKit, or enterprise-ready identity features. Covers WorkOS AuthKit, SSO, directory sync, admin portal, and user management.

Auth Services229L

Auth0

Build with Auth0 for enterprise authentication and identity. Use this skill when the project needs to integrate Auth0 for SSO, OAuth, SAML, MFA, passwordless login, machine-to-machine auth, role-based access control, or enterprise identity federation. Covers the Auth0 Next.js SDK, React SDK, Management API, Actions, and RBAC.

Auth Services270L