Skip to main content
Ganesh Joshi
Back to Blogs

Auth.js with Next.js: authentication in the App Router

February 15, 20265 min read
Tutorials
Auth or security code on screen

Auth.js (the project formerly known as NextAuth) is a flexible auth library for Next.js. It supports OAuth providers (Google, GitHub, etc.), credentials auth, and database-backed sessions. With the App Router, you use route handlers and Server Component integration.

What Auth.js provides

Feature Description
OAuth providers Google, GitHub, Discord, and 50+ others
Credentials auth Email/password with your own logic
Session strategies JWT (stateless) or database (stateful)
Callbacks Customize JWT, session, and sign-in behavior
TypeScript support Full types for session and configuration

Auth.js handles the OAuth flow, session management, and CSRF protection. You configure providers and callbacks.

Installation and setup

1. Install Auth.js

npm install next-auth@beta

Use the beta for App Router support, or check the current stable version.

2. Create the route handler

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

import NextAuth from 'next-auth';
import { authConfig } from '@/lib/auth';

const handler = NextAuth(authConfig);
export { handler as GET, handler as POST };

3. Create the auth config

Create lib/auth.ts:

import type { NextAuthConfig } from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';

export const authConfig: NextAuthConfig = {
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      if (token.id) {
        session.user.id = token.id as string;
      }
      return session;
    },
  },
  pages: {
    signIn: '/login',
    error: '/auth/error',
  },
};

4. Add environment variables

AUTH_SECRET=your-random-secret-at-least-32-chars
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

Generate AUTH_SECRET with openssl rand -base64 32.

OAuth providers

Auth.js supports 50+ OAuth providers. Each needs client credentials:

import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Discord from 'next-auth/providers/discord';

providers: [
  Google({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  GitHub({
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  }),
  Discord({
    clientId: process.env.DISCORD_CLIENT_ID!,
    clientSecret: process.env.DISCORD_CLIENT_SECRET!,
  }),
],

Set up OAuth apps in each provider's developer console and add the callback URL: https://yourdomain.com/api/auth/callback/google.

Credentials authentication

For email/password auth, use the Credentials provider:

import Credentials from 'next-auth/providers/credentials';
import { verifyPassword } from '@/lib/password';
import { getUserByEmail } from '@/lib/db';

providers: [
  Credentials({
    name: 'Email',
    credentials: {
      email: { label: 'Email', type: 'email' },
      password: { label: 'Password', type: 'password' },
    },
    async authorize(credentials) {
      if (!credentials?.email || !credentials?.password) {
        return null;
      }

      const user = await getUserByEmail(credentials.email as string);
      if (!user) {
        return null;
      }

      const isValid = await verifyPassword(
        credentials.password as string,
        user.passwordHash
      );
      if (!isValid) {
        return null;
      }

      return {
        id: user.id,
        email: user.email,
        name: user.name,
      };
    },
  }),
],

Never store plain passwords. Use bcrypt or Argon2 for hashing.

Session strategies

JWT sessions (default)

Session data is encoded in a cookie. No database required.

session: {
  strategy: 'jwt',
  maxAge: 30 * 24 * 60 * 60, // 30 days
},

Database sessions

Sessions are stored in a database. Supports revocation.

import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';

adapter: PrismaAdapter(prisma),
session: {
  strategy: 'database',
},
Strategy Pros Cons
JWT No database needed, fast Cannot revoke, larger cookies
Database Revocable, smaller cookies Requires database, slower

Getting the session

In Server Components

import { auth } from '@/lib/auth';

export default async function ProfilePage() {
  const session = await auth();

  if (!session) {
    redirect('/login');
  }

  return (
    <div>
      <h1>Welcome, {session.user?.name}</h1>
      <p>Email: {session.user?.email}</p>
    </div>
  );
}

In Route Handlers

import { auth } from '@/lib/auth';
import { NextResponse } from 'next/server';

export async function GET() {
  const session = await auth();

  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  return NextResponse.json({ user: session.user });
}

In Client Components

'use client';

import { useSession } from 'next-auth/react';

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

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

  if (!session) {
    return <LoginButton />;
  }

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

Wrap your app with SessionProvider:

// app/providers.tsx
'use client';

import { SessionProvider } from 'next-auth/react';

export function Providers({ children }: { children: React.ReactNode }) {
  return <SessionProvider>{children}</SessionProvider>;
}

Protecting routes

With middleware

Create middleware.ts:

import { auth } from '@/lib/auth';
import { NextResponse } from 'next/server';

export default auth((req) => {
  const isLoggedIn = !!req.auth;
  const isProtectedRoute = req.nextUrl.pathname.startsWith('/dashboard');

  if (isProtectedRoute && !isLoggedIn) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  return NextResponse.next();
});

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

In layouts

import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';

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

  if (!session) {
    redirect('/login');
  }

  return <div>{children}</div>;
}

Callbacks

Add custom fields to session

callbacks: {
  async jwt({ token, user, account }) {
    // Add user ID and role on sign-in
    if (user) {
      token.id = user.id;
      token.role = user.role;
    }
    return token;
  },
  async session({ session, token }) {
    // Make them available in session
    session.user.id = token.id as string;
    session.user.role = token.role as string;
    return session;
  },
},

Control sign-in

callbacks: {
  async signIn({ user, account, profile }) {
    // Only allow specific email domains
    if (user.email?.endsWith('@company.com')) {
      return true;
    }
    return false; // Reject sign-in
  },
},

Type augmentation

Extend session types for custom fields:

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

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

Summary

Auth.js provides flexible authentication for Next.js:

  1. Install next-auth and create the route handler
  2. Configure providers (OAuth, credentials)
  3. Choose session strategy (JWT or database)
  4. Get session with auth() in Server Components
  5. Protect routes with middleware or layout checks
  6. Customize with callbacks for custom fields

The Auth.js docs have the full API and provider list.

Frequently Asked Questions

Auth.js (formerly NextAuth.js) is a flexible authentication library for Next.js. It supports OAuth providers like Google and GitHub, credentials-based auth, and both JWT and database sessions.

Install next-auth, create a route handler at app/api/auth/[...nextauth]/route.ts, configure providers and callbacks, and use the auth() function in Server Components to get the session.

JWT is simpler and does not require a database. Database sessions support revocation and are better for sensitive apps. Choose based on your security requirements and infrastructure.

In Server Components, call auth() and redirect if no session. In middleware, check the session and redirect unauthenticated users. In Route Handlers, verify the session at the top of each handler.

Yes. Use callbacks in your Auth.js config to add fields like user ID or role to the JWT and session objects. These are then available when you call auth().

Related Posts