DEV Community

Cover image for How to authenticate a Next.js Route Handler using Clerk
ablydevin for Ably

Posted on • Originally published at ably.com

How to authenticate a Next.js Route Handler using Clerk

When building web applications you’ll often be using services on the client that require authentication. A common way client-side SDKs like Ably authenticate themselves is by using server-side API endpoints that generate and return access tokens. Using access tokens allows you to keep your service’s secrets on the server and instead issue time-limited and scope-limited tokens to a client. Some services even let you create revocable tokens for added security.

But how do you protect those token-generating endpoints? For a React application, Clerk is a simple but powerful way to add user management and authentication capabilities. In this blog post, I’ll show you how to use Clerk to protect routes in an Next.js application that users will access and how to protect the token-generating API endpoints the Ably client-side SDKs need.

If you want to skip ahead for the completed sample code, head over to this GitHub repository. Otherwise, let's start building!

Prerequisites

Before building this application you’ll need to make sure you have an Ably account. If you don’t have one you can sign up for a free trial. You will also need a Clerk account. Clerk has a generous free tier that makes it super simple to add user management to your application.

When creating your Clerk account, make sure you enable the Username attribute for your Application. We'll use that later in the post to show how you can access the user data Clerk manages for you to customize an Ably client.

Once you have accounts for Clerk and Ably you will need to locate your Clerk API Keys and Ably API key. Clerk API keys are found in the API Keys section of your Clerk applications dashboard. Ably API keys are found in the API Keys section of your Ably applications dashboard.

Finally, you’ll need to make sure you have at least Node 18 installed in order to build this project. You can check your current Node version by running the command node -v from your terminal or command line.

Application Setup

Get started building the application by using using giget to clone an Ably Next.js starter kit application:



npx giget@latest gh:ablydevin/starter-kits/next-typescript clerk-ably-nextjs


Enter fullscreen mode Exit fullscreen mode

The starter kit provides you with a Next.js application that has been preconfigured with an Ably client. Once you’ve cloned the folder, make sure you copy the .env.example file and drop in your Ably API key.

Make sure the project works as expected by installing its dependencies using npm install. Test the site by running npm run dev. The project should start and load on a local web server running on port 3000. Open the site in a browser to verify it loads successfully.

Create Next App

Once the basic site is running, you can add the infrastructure needed to use Clerk in the site.

Start by adding the dependency for the Clerk package:



npm install @clerk/nextjs


Enter fullscreen mode Exit fullscreen mode

Next set up the environment variables used to store the Clerk key and secret we’ll use for this project:



NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="[YOUR_CLERK_PUBLISHABLE_KEY]"
CLERK_SECRET_KEY="[YOUR_CLERK_SECRET_KEY]"
ABLY_API_KEY="[YOUR_ABLY_API_KEY]"


Enter fullscreen mode Exit fullscreen mode

Setting up Clerk Authentication

Let's add basic authentication to our application using Clerk. Clerk provides a set of Next.js specific components and helpers that make adding user management a breeze.

Open layout.tsx and surround the default HTML tags with a <ClerkProvider> component:



import { ClerkProvider } from "@clerk/nextjs";

// ...

return (
  <ClerkProvider>
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  </ClerkProvider>
)


Enter fullscreen mode Exit fullscreen mode

This puts our entire application within the scope of Clerk. Next, we need a route where unauthenticated users sign in. Under the app directory add a new folder named sign-in and add a new file called page.tsx to it.



import { SignIn } from "@clerk/nextjs";

export default function Page() {
  return <SignIn />;
}


Enter fullscreen mode Exit fullscreen mode

The page renders the <SignIn> component which is Clerks default UI for signing in users.

In the app folder open page.tsx. Import the UserButton component and add it to the page markup. The UserButton component is Clerks default UI for user profile management and session signout.



import { UserButton } from "@clerk/nextjs";

// ...

<main className="flex min-h-screen flex-col items-center justify-between p-24">
  <div>
      <UserButton afterSignOutUrl="/"/>
  </div>

  {/* ... The existing page markup ... */}
</main>


Enter fullscreen mode Exit fullscreen mode

Finally, add a new file named middleware.ts to the project root. This is a special file that Next.js uses to allow you to define application middleware. Middleware allows you to run code before a request is completed. Based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly. Clerk uses a helper function called authMiddleware to integrate authentication into a Next.js application.

For our application add the following code to define the paths that Clerk should protect and to configure how Clerk performs that authentication:



import { authMiddleware } from "@clerk/nextjs";

export default authMiddleware({});

export const config = {
  matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};


Enter fullscreen mode Exit fullscreen mode

The code uses Clerks recommended matcher, which by default protects all routes, with the exception of internal /_next/ routes and static files.

Let's test the application. Make sure it's still running (or run npm run dev again) and reload the site in the browser. Now when the site loads, the home page should automatically redirect to the Clerk sign-in because you haven’t authenticated yourself yet.

redirect to the Clerk sign-in

From the sign-in UI you can either use your existing Clerk user credentials to log in, or walk through the new user sign-up process. Once you can successfully authenticate you should be redirected back to the site home page.

Notice that on the site home page, there is now a user avatar. This is Clerks UserButton. Click the button and a context menu with options to edit your profile or sign out are shown.

Create Next App

Token API

As mentioned earlier the matcher we’ve configured in the middleware protects any routes in the project, including the token generation route handler that comes preconfigured in the Ably starter kit project. You can find this route handler under the app/api/ably/ folder.

In this project, the route handler is used as an API endpoint. When requested, it generates and returns a Token Request the Ably Client uses to connect to Ably. Token Requests are one of several methods available from Ably that allow you to authenticate an Ably Client securely without exposing your API secret keys.

Try loading the route handler in the browser by loading http://localhost:3000/api/ably. When you load the route you will be redirected to the sign-in page because we’ve configured Clerk to protect all routes the same way - even the API route. Having an API endpoint redirect to a signup isn’t really the behavior that we want. Humans won’t be using this endpoint, browsers will, so we should return something a browser will understand like a 401 Unauthorized status code.

To accommodate this different expectation of API endpoints, Clerk includes an apiRoutes property that you can pass to the authMiddleware function in our middleware.ts file.



export default authMiddleware({
  apiRoutes: ["/api(.*)"],
});


Enter fullscreen mode Exit fullscreen mode

Try loading the /api/ably/ endpoint again in the browser. This time it should return the 401 status code as we expect. Trying to load the root route of the website should continue to redirect to the sign-in page.

Log in to the site using the sign-in and then return to the /api/ably/ endpoint. This time, because you’ve created an authorized session, the API endpoint should return an Ably token.

Api Endpoint

Clerk Username as Ably Client ID

In our Token Request endpoint we’re randomly generating a string of characters to use as the Ably Client ID. Randomly generator strings okay but because we’re using Clerk we have actual user data, like a username. We can access the user data and use it as our client ID instead of a random number.

To do this import the currentUser function into the route.ts file:



import { currentUser } from "@clerk/nextjs";


Enter fullscreen mode Exit fullscreen mode

Calling the currentUser function returns a user object which we can use to access the user's username property. Change the value of the clientId property that is passed into the createTokenRequest function from a hard-coded string to the users username:



export async function GET(request: Request) {
  const user = await currentUser();

  // ...

  //const clientId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);

  const client = new Ably.Rest(process.env.NEXT_ABLY_API_KEY as string);

  const tokenRequestData = await client.auth.createTokenRequest({
    clientId: user?.username as string 
  });
  console.log(`Request: ${JSON.stringify(tokenRequestData)}`)
  return Response.json(tokenRequestData);
}


Enter fullscreen mode Exit fullscreen mode

Reload the /api/ably/ endpoint and you should now see that the token contains the username.

token contains the username

Lastly, display the connected client ID in the site by altering the Status.tsx component. Import the useAbly hook which returns an instance of the Ably client object. Use that instance to access and display the Client ID on the page.



'use client'
import Image from 'next/image'
import { useState } from 'react'
import { useConnectionStateListener, useAbly } from 'ably/react'
export default function Status() {
  const client = useAbly();
    const [state, setState] = useState('')
    useConnectionStateListener((stateChange) => {
      setState(stateChange.current)
    });

    return (
      <div>
        <p><span>Realtime Connection Status: {state}</span></p>
        <p><span>ClientId: {client.auth.clientId}</span></p>
      </div>
    )
}


Enter fullscreen mode Exit fullscreen mode

Working App

Yay!

Wrapup

Clerk is a great tool for adding user management and application authentication to a React application. It makes it easy not only to protect React routes users will access, but also routes that client-side SDKs need to access.

In this post, I showed you how to use Clerk to protect an API route that generates Ably Token Requests. Token Requests are used by the ably-js client library to authenticate with Ably and allow you to add realtime experiences to your React application.

Additionally, I showed you how you access the user profile data managed by Clerk and use that as part of the Token Request generation process, setting the Ably client ID to the user’s username.

User management is a complex part of many websites. Clerk makes it easy to add to React applications. It would be simple to use Clerk to manage other parts of realtime applications like usernames in an Ably-powered chat application or tracking a Clerk user’s current status using Ablys presence capabilities.

Let me know how you are using Clerk in your application! Drop me a line on X (Twitter) or via an email to devin.rader@ably.com.

Top comments (1)

Collapse
 
fmerian profile image
flo merian

thanks for this tutorial, @ablydevin!

oh and you're featured in our latest product update!