NextReady provides a robust authentication system built on Next-Auth, supporting multiple authentication methods including credentials, Google OAuth, and more.
NextReady uses Next-Auth (NextAuth.js) for authentication, providing a secure, flexible, and easy-to-use authentication system. Next-Auth supports multiple authentication providers and handles sessions, JWT tokens, and database integration.
NextReady's authentication system is configured in the src/lib/auth-config.ts
file. This file sets up NextAuth.js with the necessary providers, callbacks, and database adapter.
// src/lib/auth-config.ts
import { NextAuthOptions } from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import CredentialsProvider from "next-auth/providers/credentials"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import clientPromise from "./mongodb-client"
import dbConnect from "./mongodb"
import User from "@/models/User"
import bcrypt from "bcryptjs"
export const authOptions: NextAuthOptions = {
adapter: MongoDBAdapter(clientPromise),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null
}
await dbConnect()
const user = await User.findOne({ email: credentials.email })
if (!user || !user.password) {
return null
}
const isPasswordValid = await bcrypt.compare(
credentials.password,
user.password
)
if (!isPasswordValid) {
return null
}
return {
id: user._id.toString(),
name: user.name,
email: user.email,
image: user.image,
role: user.role
}
}
})
],
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role
token.id = user.id
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.role = token.role
session.user.id = token.id
}
return session
}
},
pages: {
signIn: "/auth/signin",
error: "/auth/error",
},
secret: process.env.NEXTAUTH_SECRET,
}
To configure authentication, you need to set the following environment variables in your .env.local
file:
# NextAuth Configuration
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key-here
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NextReady has a known dependency conflict between next-auth
, @auth/core
and @auth/mongodb-adapter
. To resolve this, make sure to add a .npmrc
file with legacy-peer-deps=true
to your project.
NextReady supports multiple authentication providers through Next-Auth. By default, it includes:
The Credentials provider allows users to log in with an email and password. This is configured in the auth-config.ts
file:
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Authentication logic
}
})
The Google provider allows users to log in with their Google account. To set up Google authentication:
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
})
You can easily add more authentication providers by installing the necessary packages and adding them to the providers
array in auth-config.ts
:
// Example: Adding GitHub provider
import GitHubProvider from "next-auth/providers/github"
// Add to providers array
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
})
NextReady uses JWT-based session management. Sessions are stored in cookies and can be accessed on both the client and server side.
You can access the session on the client side using the useSession
hook:
// Client component
'use client'
import { useSession } from "next-auth/react"
export default function ProfileButton() {
const { data: session, status } = useSession()
if (status === "loading") {
return <div>Loading...</div>
}
if (status === "unauthenticated") {
return <button>Sign In</button>
}
return (
<div>
<p>Welcome, {session?.user?.name}</p>
<button>View Profile</button>
</div>
)
}
You can access the session on the server side using the getServerSession
function:
// Server component or API route
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth-config"
export async function getData() {
const session = await getServerSession(authOptions)
if (!session) {
// Handle unauthenticated user
return { error: "Not authenticated" }
}
// Access user information
const userId = session.user.id
// Fetch user-specific data
// ...
return { userData: "..." }
}
NextReady includes middleware to protect routes that require authentication.
You can protect client-side routes using the useSession
hook and redirecting unauthenticated users:
// Client component
'use client'
import { useSession } from "next-auth/react"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
export default function ProtectedPage() {
const { data: session, status } = useSession()
const router = useRouter()
useEffect(() => {
if (status === "unauthenticated") {
router.push("/auth/signin?callbackUrl=/protected-page")
}
}, [status, router])
if (status === "loading") {
return <div>Loading...</div>
}
if (!session) {
return null
}
return (
<div>
<h1>Protected Content</h1>
<p>This page is only visible to authenticated users.</p>
</div>
)
}
You can protect server-side routes by checking the session and redirecting if necessary:
// Server component
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth-config"
import { redirect } from "next/navigation"
export default async function ProtectedServerPage() {
const session = await getServerSession(authOptions)
if (!session) {
redirect("/auth/signin?callbackUrl=/protected-page")
}
return (
<div>
<h1>Protected Server Content</h1>
<p>This page is only visible to authenticated users.</p>
</div>
)
}
NextReady supports role-based access control. You can check the user's role to determine if they have access to certain features:
// Check if user is an admin
if (session?.user?.role === "admin") {
// Show admin features
} else {
// Show regular user features or redirect
redirect("/dashboard")
}
NextReady includes a custom registration system that works alongside Next-Auth.
The registration API is located at /api/auth/signup
and handles creating new user accounts:
// src/app/api/auth/signup/route.ts
import { NextResponse } from "next/server"
import bcrypt from "bcryptjs"
import dbConnect from "@/lib/mongodb"
import User from "@/models/User"
export async function POST(request: Request) {
try {
const { name, email, password } = await request.json()
// Validate input
if (!name || !email || !password) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
)
}
await dbConnect()
// Check if user already exists
const existingUser = await User.findOne({ email })
if (existingUser) {
return NextResponse.json(
{ error: "User already exists" },
{ status: 409 }
)
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10)
// Create new user
const newUser = await User.create({
name,
email,
password: hashedPassword,
role: "user",
})
// Remove password from response
const user = {
id: newUser._id.toString(),
name: newUser.name,
email: newUser.email,
role: newUser.role,
}
return NextResponse.json(user, { status: 201 })
} catch (error) {
console.error("Registration error:", error)
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
)
}
}
NextReady includes a registration form component that submits to the registration API:
// Client component
'use client'
import { useState } from "react"
import { signIn } from "next-auth/react"
import { useRouter } from "next/navigation"
export default function SignUpForm() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError("")
try {
// Register user
const response = await fetch("/api/auth/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, password }),
})
if (!response.ok) {
const data = await response.json()
throw new Error(data.error || "Registration failed")
}
// Sign in after successful registration
const result = await signIn("credentials", {
email,
password,
redirect: false,
})
if (result?.error) {
throw new Error(result.error || "Sign in failed")
}
// Redirect to dashboard
router.push("/dashboard")
} catch (error: any) {
setError(error.message)
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit}>
{error && (
<div className="mb-4 p-3 bg-red-50 text-red-500 rounded-lg">
{error}
</div>
)}
<div className="mb-4">
<label htmlFor="name" className="block mb-2 text-sm font-medium">
Name
</label>
<input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="block mb-2 text-sm font-medium">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div className="mb-6">
<label htmlFor="password" className="block mb-2 text-sm font-medium">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
required
minLength={8}
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{loading ? "Creating Account..." : "Sign Up"}
</button>
</form>
)
}
NextReady includes an admin authentication system that restricts access to administrative features.
You can create middleware to protect admin routes:
// src/middleware.ts
import { NextResponse } from "next/server"
import { getToken } from "next-auth/jwt"
import type { NextRequest } from "next/server"
export async function middleware(request: NextRequest) {
// Check for admin routes
if (request.nextUrl.pathname.startsWith("/admin")) {
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
})
// Check if user is authenticated and has admin role
if (!token || token.role !== "admin") {
return NextResponse.redirect(new URL("/auth/signin", request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: ["/admin/:path*"],
}
You can protect admin API routes by checking the user's role:
// src/app/api/admin/route.ts
import { NextResponse } from "next/server"
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth-config"
export async function GET(request: Request) {
const session = await getServerSession(authOptions)
// Check if user is authenticated and has admin role
if (!session || session.user.role !== "admin") {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
)
}
// Admin-only logic here
return NextResponse.json({ data: "Admin data" })
}
You can extend NextReady's authentication system with custom logic to meet your specific requirements.
You can customize Next-Auth's behavior by modifying the callbacks in auth-config.ts
:
callbacks: {
async signIn({ user, account, profile }) {
// Custom sign-in logic
// Return true to allow sign in, false to deny
return true
},
async redirect({ url, baseUrl }) {
// Custom redirect logic
return url.startsWith(baseUrl) ? url : baseUrl
},
async jwt({ token, user, account }) {
// Add custom properties to JWT token
if (user) {
token.customProperty = user.customProperty
}
return token
},
async session({ session, token }) {
// Add custom properties to session
if (session.user) {
session.user.customProperty = token.customProperty
}
return session
}
}
You can customize the authentication pages by specifying them in the pages
option:
pages: {
signIn: "/auth/signin",
signOut: "/auth/signout",
error: "/auth/error",
verifyRequest: "/auth/verify-request",
newUser: "/auth/new-user"
}
You can listen for authentication events using the next-auth/react
event listeners:
// Client component
'use client'
import { useEffect } from "react"
import { signIn, signOut, useSession } from "next-auth/react"
export default function AuthEvents() {
const { data: session } = useSession()
useEffect(() => {
const handleSignIn = (message: any) => {
console.log("User signed in", message)
// Custom logic after sign in
}
const handleSignOut = (message: any) => {
console.log("User signed out", message)
// Custom logic after sign out
}
// Add event listeners
window.addEventListener("signIn", handleSignIn)
window.addEventListener("signOut", handleSignOut)
// Clean up
return () => {
window.removeEventListener("signIn", handleSignIn)
window.removeEventListener("signOut", handleSignOut)
}
}, [])
return (
<div>
{session ? (
<button onClick={() => signOut()}>Sign Out</button>
) : (
<button onClick={() => signIn()}>Sign In</button>
)}
</div>
)
}
Now that you understand how authentication works in NextReady, you might want to explore: