NextAuth v5 Credentials with Next 14 App Router & TypeScript
NextAuth v5 Credentials with Next 14 App Router & TypeScript
NextAuth.js is a popular authentication library for Next.js applications that simplifies handling authentication for both providers like Google, GitHub, and custom credential-based authentication.
Enroll Now
With the release of NextAuth.js v5 and Next.js 14’s App Router, there are significant changes and improvements that developers should take into account when working with credentials-based authentication. Additionally, integrating TypeScript further enhances the developer experience by providing type safety and autocompletion, which is increasingly important in modern web development.
In this guide, we’ll explore how to set up credentials-based authentication using NextAuth v5 in a Next.js 14 project with the App Router and TypeScript.
Prerequisites
Before diving into the implementation, make sure you have the following installed on your system:
- Node.js v18 or higher
- Next.js v14 (which includes the new App Router)
- NextAuth.js v5
- A basic understanding of TypeScript
Setting Up the Project
Let’s start by setting up a new Next.js 14 project with TypeScript and NextAuth.js v5.
Initialize a Next.js Project:
Run the following command to create a new Next.js project:
bashnpx create-next-app@latest my-next-auth-app --typescript cd my-next-auth-app
Install NextAuth.js:
After setting up the project, install NextAuth.js:
bashnpm install next-auth
If you are using a custom database, you might also need to install a database adapter for NextAuth.js, but for the purpose of credentials-based authentication, we won’t need that in this guide.
Folder Structure with App Router:
Next.js 14 introduces the App Router, which encourages better separation of concerns between API routes and UI components. Instead of the old
pages/api/auth/[...nextauth].ts
setup, we’ll use the new file-based routing under theapp
directory.Here’s how the basic folder structure should look like:
lua├── app │ ├── api │ │ ├── auth │ │ │ └── route.ts ├── components ├── styles ├── next.config.js └── ...
The new
route.ts
file underapp/api/auth/
will handle authentication via NextAuth.js.
Configuring NextAuth.js v5 with Credentials Provider
Setting Up the Auth Route:
Next, we’ll set up NextAuth.js under the
route.ts
file in theapp/api/auth/
directory. First, import the necessary dependencies and configure the NextAuth handler.typescript// app/api/auth/route.ts import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { NextAuthOptions } from "next-auth"; // Configure the NextAuth options const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: "Credentials", credentials: { username: { label: "Username", type: "text", placeholder: "your username" }, password: { label: "Password", type: "password", placeholder: "your password" }, }, async authorize(credentials) { const { username, password } = credentials ?? {}; // Add logic to verify username and password (e.g., query a database) if (username === "admin" && password === "admin123") { // If the credentials are valid, return a user object return { id: 1, name: "Admin", email: "admin@example.com" }; } // If the credentials are invalid, return null return null; }, }), ], pages: { signIn: "/auth/signin", // Custom sign-in page }, callbacks: { async jwt({ token, user }) { // Persist the user info in the JWT if (user) { token.id = user.id; } return token; }, async session({ session, token }) { // Attach the user ID to the session if (token && session.user) { session.user.id = token.id as number; } return session; }, }, session: { strategy: "jwt", }, }; // Export the NextAuth handler const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
In the above code, we define the
CredentialsProvider
, which expects a username and password. In theauthorize
method, we simulate user authentication by hardcoding a check for the username and password. In a real-world application, you would query your database or use an external API to validate user credentials.Custom Sign-In Page:
NextAuth.js allows for custom sign-in pages. In this example, we specify that the sign-in page is located at
/auth/signin
. You can create a simple sign-in page in theapp/auth/signin/page.tsx
file:tsx// app/auth/signin/page.tsx import { signIn } from "next-auth/react"; import { useState } from "react"; export default function SignInPage() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await signIn("credentials", { username, password, callbackUrl: "/dashboard", // Redirect after sign-in }); }; return ( <div> <h1>Sign In</h1> <form onSubmit={handleSubmit}> <label> Username: <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} /> </label> <br /> <label> Password: <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </label> <br /> <button type="submit">Sign In</button> </form> </div> ); }
This page includes a basic form to capture the username and password. The
signIn
function fromnext-auth/react
is used to submit the credentials to the credentials provider.
Securing Pages and Routes
To secure routes or pages, NextAuth.js provides the useSession
hook that lets you check if a user is authenticated. Here’s how to protect a dashboard page from unauthenticated users:
Protecting a Page:
Create a dashboard page at
app/dashboard/page.tsx
and protect it using theuseSession
hook:tsx// app/dashboard/page.tsx import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; export default function Dashboard() { const { data: session, status } = useSession(); const router = useRouter(); useEffect(() => { if (status === "unauthenticated") { router.push("/auth/signin"); } }, [status, router]); if (status === "loading") { return <div>Loading...</div>; } return ( <div> <h1>Welcome, {session?.user?.name}</h1> <p>This is a protected dashboard page.</p> </div> ); }
In this code, the
useSession
hook retrieves the current user’s session. If the user is unauthenticated, they are redirected to the sign-in page.Middleware for Route Protection:
You can also use Next.js middleware to protect entire routes. For example, if you want to protect all pages under
/dashboard
, create a middleware atmiddleware.ts
:typescript// middleware.ts import { NextRequest, NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; export async function middleware(req: NextRequest) { const token = await getToken({ req }); const isAuth = !!token; const url = req.nextUrl.clone(); if (!isAuth && url.pathname.startsWith("/dashboard")) { url.pathname = "/auth/signin"; return NextResponse.redirect(url); } return NextResponse.next(); } export const config = { matcher: ["/dashboard/:path*"], };
The middleware checks if a user has a valid token (i.e., is authenticated) and redirects them to the sign-in page if they attempt to access a protected route.
Conclusion
In this guide, we’ve walked through the setup of credentials-based authentication using NextAuth.js v5 with Next.js 14’s App Router and TypeScript. You’ve learned how to:
- Configure NextAuth.js with the new App Router.
- Set up a credentials provider to handle custom authentication logic.
- Create a custom sign-in page using TypeScript and React.
- Secure pages and routes using both the
useSession
hook and Next.js middleware.
This combination of technologies offers a powerful and flexible foundation for handling authentication in Next.js applications, ensuring scalability and maintainability.