Skip to content Skip to sidebar Skip to footer

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.

  1. Initialize a Next.js Project:

    Run the following command to create a new Next.js project:

    bash
    npx create-next-app@latest my-next-auth-app --typescript cd my-next-auth-app
  2. Install NextAuth.js:

    After setting up the project, install NextAuth.js:

    bash
    npm 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.

  3. 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 the app 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 under app/api/auth/ will handle authentication via NextAuth.js.

Configuring NextAuth.js v5 with Credentials Provider

  1. Setting Up the Auth Route:

    Next, we’ll set up NextAuth.js under the route.ts file in the app/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 the authorize 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.

  2. 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 the app/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 from next-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:

  1. Protecting a Page:

    Create a dashboard page at app/dashboard/page.tsx and protect it using the useSession 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.

  2. 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 at middleware.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.

Quasar 2 & Firebase Cloud Firestore (with Vue 3 & Pinia) Udemy