tutorial
14 min read
4/27/2024

User Authentication in Next.js 14: A Simple Step-by-Step Tutorial

Learn how Clerk simplifies user authentication! Follow this step-by-step tutorial on how to implement secure login features on both the client and server sides of your Next.js 14 app in under 5 minutes! Style components and pages using the popular Shadcn-UI library with its new update, blocks.

Article Thumbnail
nextjs
typescript
shadcn-ui
clerk
tailwindcss
tailwind
shadcn
next
react
reactjs
## Next.js Installation Let's begin by creating a new Next.js project using the `create-next-app` utility: ```bash title="terminal" npx create-next-app@latest ``` During installation, you'll encounter the following prompts: ```terminal title="terminal" √ What is your project named? ... my-app √ Would you like to use TypeScript? ... No / _Yes_ √ Would you like to use ESLint? ... No / _Yes_ √ Would you like to use Tailwind CSS? ... No / _Yes_ √ Would you like to use `src/` directory? ... _No_ / Yes √ Would you like to use App Router? (recommended) ... No / _Yes_ √ Would you like to customize the default import alias (@/*)? ... No / _Yes_ √ What import alias would you like configured? ... ~/* ``` Once you complete the prompts, `create-next-app` will establish a new project directory under your chosen project name and install the necessary dependencies. After the project setup is complete, navigate to your project directory: ```bash title="terminal" cd my-app ``` ## Install Shadcn-UI Next, let’s incorporate the `shadcn-ui` library into our project. Execute the `shadcn-ui` initialization command to configure your project: ```bash title="terminal" npx shadcn-ui@latest init ``` You will be prompted to answer several questions to configure the `components.json` file: ```terminal title="terminal" √ Which style would you like to use? » _Default_ √ Which color would you like to use as base color? » _Neutral_ √ Would you like to use CSS variables for colors? ... no / _yes_ ✔ Writing components.json... ✔ Initializing project... ✔ Installing dependencies... Success! Project initialization completed. You may now add components. ``` ## Install @clerk/nextjs Now, we'll add the `clerk` library, a vital component for user authentication. The Clerk Next.js SDK includes prebuilt components, React hooks, and helpers to simplify the integration of user authentication. Add this SDK to our project: ```bash title="terminal" npm install @clerk/nextjs ``` Before proceeding further, test the project by starting the development server with the following command: ```bash title="terminal" npm run dev ``` If you navigate to http://localhost:3000, you should see your application running. Go to the [Clerk website](https://clerk.com/), and sign in using your preferred method. Create your `<SignIn />`, choose your "Application name," and select your "Sign in options," then click on the "Create Application" button. You will be directed to a page where you can select your framework. Choose Next.js and follow the instructions provided: ### Set your environment variables Add the following keys to your `.env.local` file. You can retrieve these keys from the API Keys section of your Clerk Dashboard. ```env title=".env.local" NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_********************************************* CLERK_SECRET_KEY=sk_test_****************************************** ``` ### Add Middleware to your application `clerkMiddleware()` provides access to the user authentication state throughout your application on any route or page. It also enables you to protect specific routes from unauthenticated users. To implement `clerkMiddleware()`, follow these steps: Create a `middleware.ts` file and place it in the root directory alongside `.env.local.` In this file, export Clerk's `clerkMiddleware()` helper: ```ts title="middleware.ts" import { clerkMiddleware } from "@clerk/nextjs/server"; export default clerkMiddleware(); export const config = { matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], }; ``` By default, `clerkMiddleware()` does not protect any routes. You must opt-in to protection for specific routes. ### Add `<ClerkProvider>` and components to your app To make Clerk's session and user context data available throughout your app, incorporate the `<ClerkProvider>` component into your `layout.tsx` file: ```tsx title="layout.tsx" {4,19,23} import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { ClerkProvider } from "@clerk/nextjs"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <ClerkProvider> <html lang="en"> <body className={inter.className}>{children}</body> </html> </ClerkProvider> ); } ``` Using Clerk's prebuilt components, you can easily control the visibility of content for users depending on their authentication state. Start by creating a user-friendly header that allows users to sign in or out. Here's how you can implement this: `<SignedIn>`: This component displays its children only when the user is signed in. `<SignedOut>`: This component displays its children only when the user is signed out. `<UserButton />`: A stylish component that displays the user's avatar when signed in. `<SignInButton />`: A basic component that links to the sign-in page or displays the sign-in modal. Let's update our `layout.tsx` file to include the above components: ```tsx title="layout.tsx" {6-9, 28-36} import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { ClerkProvider, SignInButton, SignedIn, SignedOut, UserButton, } from "@clerk/nextjs"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <ClerkProvider> <html lang="en"> <body className={inter.className}> <header> <SignedOut> <SignInButton /> </SignedOut> <SignedIn> <UserButton /> </SignedIn> </header> <main>{children}</main> </body> </html> </ClerkProvider> ); } ``` After making these changes, visit http://localhost:3000 while signed out to see the sign-in button. Once signed in, the user button will appear. ## Create Custom Sign-In and Sign-Up Pages Craft personalized sign-in and sign-up pages for your Next.js app with Clerk. ### Develop a Sign-Up Page First, create a new directory within the app folder named `auth`. Inside, create a subdirectory called `sign-up` and another within it named `[[...sign-up]]`. Within this last directory, create a `page.tsx` file and include the `<SignUp />` component from `@clerk/nextjs` to render the sign-up page. ```title="app/auth/sign-up/[[...sign-up]]/page.tsx" 📁 app ├ 📁 auth . └ 📁 sign-up . └ 📁 [[...sign-up]] . └ 📄 page.tsx ``` ```tsx title="page.tsx" import { SignUp } from "@clerk/nextjs"; export default function SignUpPage() { return <SignUp path="/auth/sign-up" />; } ``` ### Develop a Sign-In Page Follow a similar structure for the `sign-in` page by creating a new file within the same directory structure, importing the `<SignIn />` component from `@clerk/nextjs`. ```title="app/auth/sign-in/[[...sign-in]]/page.tsx" 📁 app ├ 📁 auth │ ├ 📁 sign-up │ │ └ 📁 [[...sign-up]] │ │ └ 📄 page.tsx . └ 📁 sign-in . └ 📁 [[...sign-in]] . └ 📄 page.tsx ``` ```tsx title="page.tsx" import { SignIn } from "@clerk/nextjs"; export default function Page() { return <SignIn path="/auth/sign-in" />; } ``` ### Update Your Environment Variables In the previous steps, a path prop is passed to the `<SignIn />` and `<SignUp />` components. This is because the components need to know which route they are originally mounted on. In Next.js applications, you can either pass the path prop, or you can define the `NEXT_PUBLIC_CLERK_SIGN_IN_URL` and `NEXT_PUBLIC_CLERK_SIGN_UP_URL` environment variables, like so: ```env title=".env.local" NEXT_PUBLIC_CLERK_SIGN_IN_URL=/auth/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/auth/sign-up ``` ### Visit Your New Pages Access your newly created pages locally at http://localhost:3000/auth/sign-in and http://localhost:3000/auth/sign-up. ## Styling Components and Pages Now that we have everything set up, let's style our components and pages to enhance their appeal and usability for users. ### Navbar Component First, we'll tackle the `navbar` component. Before we begin, we need to install the `Button` component from the `shadcn-ui` library: ```bash title="terminal" npx shadcn-ui@latest add button ``` The command above will add the `Button` component to your project, which you can find in the `components`/`ui` directory. Next, create a new file called `navbar.tsx` in the `components` folder. ```tsx title="navbar.tsx" import { SignInButton, SignedIn, SignedOut, UserButton } from "@clerk/nextjs"; import { Button } from "~/components/ui/button"; export function Navbar() { return ( <nav className="sticky z-40 top-0 backdrop-blur-xl bg-background/50"> <div className="max-w-screen-lg w-full flex-1 mx-auto px-6 flex items-center justify-between h-24"> <div className="text-lg font-semibold hover:opacity-70">App logo</div> <SignedOut> <Button asChild> <SignInButton /> </Button> </SignedOut> <SignedIn> <UserButton /> </SignedIn> </div> </nav> ); } ``` We also need to import it into our `layout.tsx` file and clean up any previous changes by removing the header component and its imports, as we've now created our own: ```tsx title="layout.tsx" {5, 23} import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { ClerkProvider } from "@clerk/nextjs"; import { Navbar } from "~/components/navbar"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <ClerkProvider> <html lang="en"> <body className={inter.className}> <Navbar /> <main>{children}</main> </body> </html> </ClerkProvider> ); } ``` ### Authentication Page With the navbar complete, let's move on to styling our authentication page using the `shadcn-ui` library, which provides ready-to-use code blocks. I highly recommend checking it out, as it greatly accelerates development time. Visit [shadcn-ui blocks →](https://ui.shadcn.com/blocks). For this task, we will use the `authentication-04` block. Begin by creating an authentication component in the `components` directory alongside `navbar.tsx` and name it `auth.tsx`. ```title="components/auth.tsx" {3} 📁 components ├ 📁 ui ├ 📄 auth.tsx ├ 📄 navbar.tsx ``` ```tsx title="auth.tsx" import Image from "next/image"; import Link from "next/link"; import { ReactNode } from "react"; export function Auth({ children }: { children: ReactNode }) { return ( <div className="absolute top-0 left-0 right-0 bottom-0 z-50 w-full max-h-screen flex overflow-hidden bg-background"> <Link href="/" className="absolute top-6 left-8"> ← Home </Link> <div className="w-full lg:grid lg:min-h-[600px] lg:grid-cols-2 xl:min-h-[800px]"> <div className="flex items-center justify-center py-12">{children}</div> <div className="hidden bg-muted lg:block"> <Image src="/placeholder.svg" alt="Image" width="1920" height="1080" className="h-full w-full object-cover dark:brightness-[0.2] dark:grayscale" /> </div> </div> </div> ); } ``` We also need to create a `placeholder.svg` file and place it in the `public` directory. You can replace this with any image of your choice; just remember to update the `placeholder.svg` in the `Image` component to reflect your chosen file name. ```title="public/placeholder.svg" {3} 📁 public ├ 📄 next.svg ├ 📄 placeholder.svg ├ 📄 vercel.svg ``` ```svg title="placeholder.svg" <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"> <rect width="1200" height="1200" fill="#EAEAEA" rx="3"/> <g opacity=".5"> <g opacity=".5"> <path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/> <path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/> </g> <path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/> <path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/> <path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/> <path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/> <path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/> <g clip-path="url(#e)"> <path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/> </g> <path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/> </g> <defs> <linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"> <stop stop-color="#C9C9C9" stop-opacity="0"/> <stop offset=".208" stop-color="#C9C9C9"/> <stop offset=".792" stop-color="#C9C9C9"/> <stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/> </linearGradient> <linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"> <stop stop-color="#C9C9C9" stop-opacity="0"/> <stop offset=".208" stop-color="#C9C9C9"/> <stop offset=".792" stop-color="#C9C9C9"/> <stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/> </linearGradient> <linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"> <stop stop-color="#C9C9C9" stop-opacity="0"/> <stop offset=".208" stop-color="#C9C9C9"/> <stop offset=".792" stop-color="#C9C9C9"/> <stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/> </linearGradient> <linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"> <stop stop-color="#C9C9C9" stop-opacity="0"/> <stop offset=".208" stop-color="#C9C9C9"/> <stop offset=".792" stop-color="#C9C9C9"/> <stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/> </linearGradient> <clipPath id="e"> <path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/> </clipPath> </defs> </svg> ``` Now, let's wrap our `sign-in` (app/auth/sign-in/[[...sign-in]]/page.tsx) and `sign-up` (app/auth/sign-up/[[...sign-up]]/page.tsx) components with our newly created `auth.tsx` component: ```tsx title="page.tsx" {3, 7, 9} import { SignIn } from "@clerk/nextjs"; import { Auth } from "~/components/auth"; export default function Page() { return ( <Auth> <SignIn path="/auth/sign-in" /> </Auth> ); } ``` ```tsx title="page.tsx" {3, 7, 9} import { SignUp } from "@clerk/nextjs"; import { Auth } from "~/components/auth"; export default function SignUpPage() { return ( <Auth> <SignUp path="/auth/sign-up" /> </Auth> ); } ``` ## Conclusion And that's it! It's as simple as that. I hope you found this tutorial helpful. Happy coding!
Author
Loading...
© 2024 DEVCreated