Authentication

Jumbo React Next uses Auth.js v5 (next-auth@5.0.0-beta) with a Credentials provider and JWT sessions. The auth config lives in auth.ts at the project root and exports the four primary utilities: auth, handlers, signIn, and signOut.

Architecture overview

auth.ts                           ← Auth.js config + exports
src/app/api/auth/[...nextauth]/   ← Route handler (GET + POST)
src/components/providers/AuthProvider.tsx  ← SessionProvider for client components
src/lib/auth/index.ts             ← Server-side helpers (getSession, isAuthenticated, hasRole)
src/proxy/auth.ts                 ← Proxy-level route guards
next-auth.d.ts                    ← Session/JWT type augmentation

auth.ts — Core configuration

typescript
// auth.ts
import type { NextAuthConfig } from 'next-auth';
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
 
const authConfig: NextAuthConfig = {
  providers: [
    Credentials({
      name: 'Credentials',
      credentials: {
        email:    { label: 'Email',    type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        // Replace this block with your real database + password hash check
        const demoEmail    = process.env.DEMO_USER_EMAIL    || 'demo@example.com';
        const demoPassword = process.env.DEMO_USER_PASSWORD || 'demo123';
 
        if (credentials.email === demoEmail && credentials.password === demoPassword) {
          return { id: '1', name: 'David Tim', email: demoEmail, role: 'admin', accessToken: '' };
        }
 
        return null;  // null = invalid credentials
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id          = user.id;
        token.role        = user.role;
        token.accessToken = user.accessToken;
      }
      return token;
    },
    async session({ session, token }) {
      if (token && session.user) {
        session.user.id          = token.id as string;
        session.user.role        = token.role as string;
        session.user.accessToken = token.accessToken as string;
      }
      return session;
    },
  },
  pages: {
    signIn: '/auth/login-1',
    error:  '/auth/error',
  },
  session: { strategy: 'jwt' },
};
 
export const { auth, handlers, signIn, signOut } = NextAuth(authConfig);

Session type augmentation

next-auth.d.ts extends the default Session, User, and JWT interfaces to include the custom fields:

typescript
// next-auth.d.ts
import type { DefaultSession, DefaultUser } from 'next-auth';
 
declare module 'next-auth' {
  interface Session {
    user: {
      id:          string;
      role:        string;
      accessToken: string;
    } & DefaultSession['user'];
  }
 
  interface User extends DefaultUser {
    role:        string;
    accessToken: string;
  }
}
 
declare module 'next-auth/jwt' {
  interface JWT {
    id:          string;
    role:        string;
    accessToken: string;
  }
}

Server-side session helpers

src/lib/auth/index.ts wraps the auth() function with named, intention-revealing helpers for use in server components and server actions:

typescript
// src/lib/auth/index.ts
import { auth } from '@/auth';
 
export async function getSession() {
  return await auth();
}
 
export async function isAuthenticated(): Promise<boolean> {
  const session = await auth();
  return !!session?.user;
}
 
export async function getCurrentUser() {
  const session = await auth();
  if (!session?.user) throw new Error('Not authenticated');
  return session.user;
}
 
export async function hasRole(role: string): Promise<boolean> {
  const session = await auth();
  return session?.user?.role === role;
}

Example usage in a server component:

typescript
// src/app/[lang]/(common)/user/profile-1/page.tsx
import { getCurrentUser } from '@/lib/auth';
 
export default async function Profile1Page() {
  const user = await getCurrentUser();  // throws if unauthenticated
 
  return <UserProfile1 user={user} />;
}

Client-side session access

Use SessionProvider (wrapped in AuthProvider) and the useSession hook for client components:

tsx
// src/components/providers/AuthProvider.tsx
'use client';
 
import { SessionProvider } from 'next-auth/react';
 
export function AuthProvider({ children }: { children: React.ReactNode }) {
  return <SessionProvider>{children}</SessionProvider>;
}
tsx
// In any client component
'use client';
 
import { useSession, signOut } from 'next-auth/react';
 
export function UserMenu() {
  const { data: session, status } = useSession();
 
  if (status === 'loading') return <Spinner />;
  if (!session) return null;
 
  return (
    <div>
      <span>Welcome, {session.user.name}</span>
      <button onClick={() => signOut({ callbackUrl: '/auth/login-1' })}>
        Sign out
      </button>
    </div>
  );
}

Replacing the demo Credentials provider

The demo auth is for development only. To connect real authentication:

  1. Install a database client and password hashing library

    bash
    pnpm add @prisma/client bcryptjs
    pnpm add -D @types/bcryptjs
  2. Update the authorize function in auth.ts

    Replace the demo credential check with a real database query:

    typescript
    async authorize(credentials) {
      if (!credentials?.email || !credentials?.password) return null;
     
      const user = await db.user.findUnique({
        where: { email: credentials.email as string },
      });
     
      if (!user || !user.passwordHash) return null;
     
      const isValid = await bcrypt.compare(
        credentials.password as string,
        user.passwordHash
      );
     
      if (!isValid) return null;
     
      return { id: user.id, name: user.name, email: user.email, role: user.role, accessToken: '' };
    },
  3. Set environment variables

    Update .env.local with your database URL and remove the demo credential variables:

    bash
    AUTH_SECRET=<your-secret>
    DATABASE_URL=postgresql://user:password@localhost:5432/jumbo

Adding OAuth providers

Auth.js v5 supports Google, GitHub, and many other OAuth providers. Add them alongside the existing Credentials provider:

typescript
// auth.ts
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
 
const authConfig: NextAuthConfig = {
  providers: [
    Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
    GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }),
    Credentials({ /* ... */ }),
  ],
  // ...
};