Skip to main content

Lucia vs NextAuth in 2026: Lightweight vs Full-Featured Auth

·PkgPulse Team

TL;DR

NextAuth for quick setup with OAuth; Lucia for full control over session management. NextAuth/Auth.js (~2.5M weekly downloads) handles OAuth providers, database sessions, and JWT out of the box — ideal for apps needing social login quickly. Lucia (~500K downloads) is a minimal session management library that gives you complete control over how auth works — ideal for apps with custom auth logic or email/password-focused auth.

Key Takeaways

  • NextAuth/Auth.js: ~2.5M weekly downloads — Lucia: ~500K (npm, March 2026)
  • Lucia only handles sessions — you write all auth logic (login, signup, validation)
  • NextAuth handles OAuth providers — GitHub, Google, etc. in 5 lines
  • Lucia v3 is framework-agnostic — not React/Next.js specific
  • Lucia has better TypeScript — fully typed session/user objects without hacks

What Each Library Does

NextAuth (Auth.js):
  ✓ OAuth provider integration (80+ providers)
  ✓ Database session management via adapters
  ✓ JWT session tokens
  ✓ Callback hooks (signIn, session, jwt)
  ✓ CSRF protection
  ✗ Email/password (you add CredentialsProvider — less ideal)
  ✗ Session invalidation per-device

Lucia:
  ✓ Session creation and validation
  ✓ Cookie management
  ✓ Session invalidation (individual, all-for-user, all)
  ✓ Framework adapters (Hono, Astro, SvelteKit, Express, Next.js)
  ✗ OAuth providers (use arctic.js alongside Lucia)
  ✗ CSRF protection (handle yourself)
  ✗ "Magic link" / email verification (implement yourself)

Auth Flow Comparison

// NextAuth — OAuth in minutes
// providers: GitHub + Google in 10 lines
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    GitHub({ clientId: '...', clientSecret: '...' }),
    Google({ clientId: '...', clientSecret: '...' }),
  ],
  adapter: DrizzleAdapter(db),
});

// Email/password — less clean, requires CredentialsProvider
providers: [
  Credentials({
    credentials: { email: {}, password: {} },
    authorize: async ({ email, password }) => {
      const user = await getUserByEmail(email as string);
      if (!user || !verifyPassword(password as string, user.passwordHash)) {
        return null; // Auth fails
      }
      return user;
    },
  }),
]
// Note: CredentialsProvider doesn't play well with database sessions
// Must use JWT strategy with credentials
// Lucia — full control, you build each auth flow

// 1. Session creation (after verifying credentials yourself)
import { lucia } from '@/lib/lucia';

// In your sign-in handler:
async function signIn(email: string, password: string) {
  const user = await db.query.users.findFirst({ where: eq(users.email, email) });
  if (!user || !await verifyPassword(password, user.passwordHash)) {
    throw new Error('Invalid credentials');
  }

  const session = await lucia.createSession(user.id, {});
  const sessionCookie = lucia.createSessionCookie(session.id);
  cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);

  return redirect('/dashboard');
}

// 2. Validate session (in middleware or route handlers)
async function validateRequest() {
  const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;
  if (!sessionId) return { user: null, session: null };

  const { user, session } = await lucia.validateSession(sessionId);

  if (session && session.fresh) {
    // Extend session on activity
    const sessionCookie = lucia.createSessionCookie(session.id);
    cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
  }

  return { user, session };
}

// 3. Sign out
async function signOut() {
  const { session } = await validateRequest();
  if (!session) return;
  await lucia.invalidateSession(session.id);
  const sessionCookie = lucia.createBlankSessionCookie();
  cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
  return redirect('/login');
}

OAuth with Lucia + Arctic

// Lucia + Arctic (OAuth library by the same author)
import { GitHub } from 'arctic';

const github = new GitHub(clientId, clientSecret);

// Generate auth URL
const state = generateState();
const url = await github.createAuthorizationURL(state, {
  scopes: ['user:email'],
});

// Handle callback
const tokens = await github.validateAuthorizationCode(code);
const response = await fetch('https://api.github.com/user', {
  headers: { Authorization: `Bearer ${tokens.accessToken}` },
});
const githubUser = await response.json();

// Create or get user from DB
let user = await db.query.users.findFirst({
  where: eq(oauthAccounts.providerUserId, String(githubUser.id)),
});

if (!user) {
  user = await db.transaction(async (tx) => {
    // Create user and oauth link in DB
    ...
  });
}

// Create Lucia session
const session = await lucia.createSession(user.id, {});
// ...

TypeScript Types

// NextAuth — session types require module augmentation
declare module 'next-auth' {
  interface Session {
    user: { id: string } & DefaultSession['user'] // Manual extension
  }
}

// Lucia — session type is exact, no augmentation needed
declare module 'lucia' {
  interface Register {
    Lucia: typeof lucia;
    DatabaseUserAttributes: {
      email: string;
      name: string;
    };
  }
}

// After registration:
const session = await lucia.validateSession(id);
session.user.email; // Fully typed, no any
session.user.name;  // TypeScript knows this exists

When to Choose

Choose NextAuth/Auth.js when:

  • Primary auth method is OAuth (GitHub, Google, etc.)
  • You want auth setup in under an hour
  • Your app doesn't need custom session management
  • Email/password is secondary to social login

Choose Lucia when:

  • Email/password is your primary auth method
  • You need granular control over sessions (per-device invalidation, etc.)
  • Building custom auth flows (magic links, passkeys, etc.)
  • TypeScript correctness is a top priority
  • Framework-agnostic auth library is needed

Compare Lucia and NextAuth package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.