Authentication
Complete authentication system powered by NextAuth.js with support for OAuth providers and credentials.
Overview
ShadPanel includes a pre-configured authentication system with:
- ✅ NextAuth.js v5 - Industry-standard authentication
 - ✅ Multiple Providers - Google, GitHub, credentials (email/password)
 - ✅ Session Management - Automatic session handling
 - ✅ Protected Routes - Easy route protection
 - ✅ Login/Signup Pages - Pre-built authentication UI
 - ✅ Type Safety - Full TypeScript support
 - ✅ Customizable - Extend with your own providers
 
Quick Start
Enable Authentication
When initializing a project, include authentication:
# Full panel with auth (default)
shadpanel init my-app
 
# Auth + components only
shadpanel init my-app --auth-components
 
# Choose specific providers
shadpanel init my-app --google --github --credentialsConfigure Environment Variables
Edit .env file:
# Required for NextAuth
NEXTAUTH_SECRET=your-secret-here
NEXTAUTH_URL=http://localhost:3000
 
# Google OAuth (optional)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
 
# GitHub OAuth (optional)
GITHUB_ID=your-github-id
GITHUB_SECRET=your-github-secretAuthentication Providers
Credentials (Email/Password)
Default provider for email/password authentication.
Configuration:
// app/api/auth/[...nextauth]/route.ts
import CredentialsProvider from "next-auth/providers/credentials"
 
providers: [
  CredentialsProvider({
    name: "credentials",
    credentials: {
      email: { label: "Email", type: "email" },
      password: { label: "Password", type: "password" }
    },
    async authorize(credentials) {
      // Implement your authentication logic
      const user = await authenticateUser(credentials.email, credentials.password)
 
      if (user) {
        return user
      }
      return null
    }
  })
]Login Page:
Pre-built at /admin/login
Signup Page:
Pre-built at /admin/signup
Google OAuth
Sign in with Google accounts.
Setup:
- 
Create OAuth credentials:
- Go to Google Cloud Console
 - Create a new project or select existing
 - Navigate to “APIs & Services” → “Credentials”
 - Create “OAuth 2.0 Client ID”
 - Add authorized redirect URI: 
http://localhost:3000/api/auth/callback/google 
 - 
Add to
.env:GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your-client-secret - 
Configuration:
import GoogleProvider from "next-auth/providers/google" providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }) ] 
GitHub OAuth
Sign in with GitHub accounts.
Setup:
- 
Create OAuth App:
- Go to GitHub Settings
 - Navigate to “OAuth Apps” → “New OAuth App”
 - Set Homepage URL: 
http://localhost:3000 - Set Authorization callback URL: 
http://localhost:3000/api/auth/callback/github 
 - 
Add to
.env:GITHUB_ID=your-github-client-id GITHUB_SECRET=your-github-client-secret - 
Configuration:
import GitHubProvider from "next-auth/providers/github" providers: [ GitHubProvider({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET!, }) ] 
Using Authentication
Check Session
'use client'
 
import { useSession } from 'next-auth/react'
 
export default function ProfilePage() {
  const { data: session, status } = useSession()
 
  if (status === 'loading') {
    return <div>Loading...</div>
  }
 
  if (status === 'unauthenticated') {
    return <div>Access Denied</div>
  }
 
  return (
    <div>
      <h1>Welcome, {session?.user?.name}</h1>
      <p>Email: {session?.user?.email}</p>
    </div>
  )
}Sign In/Out
'use client'
 
import { signIn, signOut, useSession } from 'next-auth/react'
import { Button } from '@/components/ui/button'
 
export default function AuthButton() {
  const { data: session } = useSession()
 
  if (session) {
    return (
      <div className="flex items-center gap-4">
        <span>Signed in as {session.user?.email}</span>
        <Button onClick={() => signOut()}>Sign Out</Button>
      </div>
    )
  }
 
  return (
    <div className="flex gap-2">
      <Button onClick={() => signIn('google')}>
        Sign in with Google
      </Button>
      <Button onClick={() => signIn('github')}>
        Sign in with GitHub
      </Button>
      <Button onClick={() => signIn('credentials')}>
        Sign in with Email
      </Button>
    </div>
  )
}Protected Pages
Client Component
'use client'
 
import { useSession } from 'next-auth/react'
import { redirect } from 'next/navigation'
 
export default function ProtectedPage() {
  const { status } = useSession({
    required: true,
    onUnauthenticated() {
      redirect('/admin/login')
    },
  })
 
  if (status === 'loading') {
    return <div>Loading...</div>
  }
 
  return <div>Protected Content</div>
}Server Component
import { getServerSession } from 'next-auth'
import { redirect } from 'next/navigation'
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
 
export default async function ProtectedPage() {
  const session = await getServerSession(authOptions)
 
  if (!session) {
    redirect('/admin/login')
  }
 
  return <div>Protected Content</div>
}Middleware Protection
// middleware.ts
import { withAuth } from 'next-auth/middleware'
 
export default withAuth({
  callbacks: {
    authorized: ({ token }) => !!token,
  },
})
 
export const config = {
  matcher: ['/admin/dashboard/:path*'],
}Pre-built Auth Pages
Login Page
Located at /admin/login:
// app/admin/login/page.tsx
import { LoginForm } from '@/components/login-form'
 
export default function LoginPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <LoginForm />
    </div>
  )
}Features:
- Email/password input
 - OAuth provider buttons
 - “Remember me” checkbox
 - “Forgot password” link
 - Link to signup page
 - Form validation
 - Error handling
 
Signup Page
Located at /admin/signup:
// app/admin/signup/page.tsx
import { SignupForm } from '@/components/signup-form'
 
export default function SignupPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignupForm />
    </div>
  )
}Features:
- Name, email, password inputs
 - Password confirmation
 - OAuth provider buttons
 - Form validation
 - Link to login page
 - Error handling
 
Session Management
Access User Data
const { data: session } = useSession()
 
// User info
session?.user?.name
session?.user?.email
session?.user?.image
 
// Custom fields (if added)
session?.user?.id
session?.user?.roleUpdate Session
import { useSession } from 'next-auth/react'
 
const { data: session, update } = useSession()
 
// Update session data
await update({
  ...session,
  user: {
    ...session?.user,
    name: 'New Name',
  },
})Session Callbacks
Customize session data:
// app/api/auth/[...nextauth]/route.ts
callbacks: {
  async session({ session, token }) {
    // Add custom fields to session
    if (session.user) {
      session.user.id = token.sub
      session.user.role = token.role
    }
    return session
  },
  async jwt({ token, user }) {
    // Add custom fields to token
    if (user) {
      token.role = user.role
    }
    return token
  },
}Customization
Custom Login Page
Replace the default login form:
'use client'
 
import { signIn } from 'next-auth/react'
import { Form, FormInput } from '@/components/ui'
import { Button } from '@/components/ui/button'
 
export default function CustomLoginPage() {
  return (
    <div className="max-w-md mx-auto">
      <h1 className="text-2xl font-bold mb-4">Sign In</h1>
 
      <Form
        initialValues={{ email: '', password: '' }}
        onSubmit={async (values) => {
          const result = await signIn('credentials', {
            email: values.email,
            password: values.password,
            redirect: false,
          })
 
          if (result?.error) {
            console.error(result.error)
          } else {
            window.location.href = '/admin/dashboard'
          }
        }}
      >
        <FormInput
          accessor="email"
          label="Email"
          type="email"
          required
        />
        <FormInput
          accessor="password"
          label="Password"
          type="password"
          required
        />
        <Button type="submit" className="w-full">
          Sign In
        </Button>
      </Form>
 
      <div className="mt-4 space-y-2">
        <Button
          variant="outline"
          className="w-full"
          onClick={() => signIn('google')}
        >
          Sign in with Google
        </Button>
        <Button
          variant="outline"
          className="w-full"
          onClick={() => signIn('github')}
        >
          Sign in with GitHub
        </Button>
      </div>
    </div>
  )
}Add Custom Provider
import type { Provider } from 'next-auth/providers'
import TwitterProvider from 'next-auth/providers/twitter'
 
const providers: Provider[] = [
  TwitterProvider({
    clientId: process.env.TWITTER_CLIENT_ID!,
    clientSecret: process.env.TWITTER_CLIENT_SECRET!,
  }),
  // ... other providers
]Custom Authentication Logic
CredentialsProvider({
  async authorize(credentials) {
    try {
      // Your custom logic
      const response = await fetch('https://your-api.com/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email: credentials.email,
          password: credentials.password,
        }),
      })
 
      const user = await response.json()
 
      if (response.ok && user) {
        return {
          id: user.id,
          email: user.email,
          name: user.name,
          role: user.role,
        }
      }
 
      return null
    } catch (error) {
      return null
    }
  }
})Database Integration
With Prisma
Store users and sessions in database:
// prisma/schema.prisma
model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  password      String?   // For credentials
  role          String    @default("user")
  accounts      Account[]
  sessions      Session[]
}
 
model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?
  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)
 
  @@unique([provider, providerAccountId])
}
 
model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}Configure adapter:
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
 
const prisma = new PrismaClient()
 
export const authOptions = {
  adapter: PrismaAdapter(prisma),
  // ... rest of config
}Security Best Practices
1. Strong Secrets
Generate secure NEXTAUTH_SECRET:
openssl rand -base64 322. HTTPS in Production
# .env.production
NEXTAUTH_URL=https://yourdomain.com3. Secure Cookies
cookies: {
  sessionToken: {
    name: `__Secure-next-auth.session-token`,
    options: {
      httpOnly: true,
      sameSite: 'lax',
      path: '/',
      secure: true
    }
  }
}4. Rate Limiting
Implement rate limiting on auth routes:
// middleware.ts
import { Ratelimit } from "@upstash/ratelimit"
 
const ratelimit = new Ratelimit({
  // ... config
})
 
export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/auth')) {
    const { success } = await ratelimit.limit(request.ip)
    if (!success) {
      return new Response('Too Many Requests', { status: 429 })
    }
  }
}Troubleshooting
”NEXTAUTH_SECRET is not set”
Add to .env:
NEXTAUTH_SECRET=$(openssl rand -base64 32)OAuth Redirect URI Mismatch
Ensure redirect URIs match in provider settings:
- Development: 
http://localhost:3000/api/auth/callback/[provider] - Production: 
https://yourdomain.com/api/auth/callback/[provider] 
Session Not Persisting
Check that SessionProvider wraps your app:
// app/layout.tsx
import { SessionProvider } from 'next-auth/react'
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <SessionProvider>{children}</SessionProvider>
      </body>
    </html>
  )
}Next Steps
- Database Integration - Store users in database
 - Protected Routes - Secure your pages
 - NextAuth.js Docs - Official documentation