Back to Blog
·7 min

Next.js Authentication Templates: Add Auth to Your App in Minutes

Authentication is the most commonly rebuilt feature in web development. Every project needs sign-up, sign-in, password reset, email verification, OAuth providers, session management, and protected routes. Setting this up from scratch in Next.js 14 involves understanding middleware, server component data access patterns, cookie management, and multiple authentication providers.

A Next.js authentication template eliminates this entirely. With a pre-built auth system, you add your credentials and start protecting routes immediately.

What a Complete Auth Template Includes

A production-ready Next.js authentication boilerplate should handle:

  • Email and password authentication
  • OAuth providers (Google, GitHub, Discord, etc.)
  • Magic link authentication
  • Session management with JWT or database sessions
  • Middleware for route protection
  • Role-based access control
  • Email verification flows
  • Password reset with secure tokens
  • MFA support

The SaaS Starter Kit includes all of this with NextAuth.js v5 pre-configured.

NextAuth.js Configuration

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

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    GitHub({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! }),
    Google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! }),
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        const user = await prisma.user.findUnique({
          where: { email: credentials.email as string },
        })
        if (!user || !user.password) return null
        const valid = await bcrypt.compare(credentials.password as string, user.password)
        return valid ? user : null
      },
    }),
  ],
  callbacks: {
    session({ session, user }) {
      session.user.id = user.id
      session.user.role = user.role
      return session
    },
  },
  pages: {
    signIn: '/login',
    error: '/login',
  },
})

Middleware for Route Protection

// middleware.ts
import { auth } from '@/lib/auth'
import { NextResponse } from 'next/server'

export default auth((req) => {
  const isLoggedIn = !!req.auth
  const isOnDashboard = req.nextUrl.pathname.startsWith('/dashboard')
  const isOnAdmin = req.nextUrl.pathname.startsWith('/admin')

  if (isOnAdmin && req.auth?.user?.role !== 'admin') {
    return NextResponse.redirect(new URL('/login', req.url))
  }

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

  return NextResponse.next()
})

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

Login Page Component

'use client'

import { signIn } from 'next-auth/react'
import { useState } from 'react'
import { useRouter } from 'next/navigation'

export function LoginForm() {
  const router = useRouter()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    const result = await signIn('credentials', {
      email,
      password,
      redirect: false,
    })
    if (result?.error) {
      setError('Invalid credentials')
    } else {
      router.push('/dashboard')
      router.refresh()
    }
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label className="text-sm font-medium">Email</label>
        <input
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          className="mt-1 w-full rounded-xl border px-4 py-2.5"
          required
        />
      </div>
      <div>
        <label className="text-sm font-medium">Password</label>
        <input
          type="password"
          value={password}
          onChange={e => setPassword(e.target.value)}
          className="mt-1 w-full rounded-xl border px-4 py-2.5"
          required
        />
      </div>
      {error && <p className="text-sm text-red-600">{error}</p>}
      <button
        type="submit"
        className="w-full rounded-xl bg-brand-600 py-2.5 font-semibold text-white"
      >
        Sign In
      </button>
      <div className="relative my-4">
        <div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div>
        <div className="relative flex justify-center"><span className="bg-white px-2 text-sm text-gray-500">or</span></div>
      </div>
      <button
        type="button"
        onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
        className="w-full rounded-xl border py-2.5 font-semibold"
      >
        Continue with Google
      </button>
    </form>
  )
}

Get started with our Next.js authentication template and add auth to your app in minutes.

Browse all 200+ templates and starter kits.

Ready to Build?

Get started with our production-ready starter kits and ship your project faster.

Browse Starter Kits