How to Authenticate API Requests with Clerk & Express
- Category
- Guides
- Published
In this tutorial, we'll explore how to use Clerk with Express to authenticate API requests using middleware.
APIs are essential for building powerful applications that can communicate and share data with other systems.
However, with great power comes great responsibility: it's critical to ensure that only authorized users can access your API, and that requests are properly authenticated and verified. Failure to do so can lead to serious security breaches, data leaks, and other vulnerabilities that can compromise the integrity of your application and put your users at risk.
In this tutorial, we'll explore how to use Clerk with Express to authenticate API requests using ClerkExpressWithAuth()
and ClerkExpressRequireAuth()
middleware, and build a secure and robust backend for your application. Let's get started!
Always authenticate
When developing an API, especially in Express.js or any other framework, it's important to authenticate requests for a number of reasons:
- Security: The most important reason is to maintain the security of your application. By authenticating API requests, you ensure that only authorized clients or users can interact with your API. This reduces the potential for malicious actions such as data theft, unauthorized modification, or even denial of service attacks.
- Access Control: It allows you to control who can access certain resources and operations in your API. For instance, certain resources might only be available to admin users, while others are available to all authenticated users.
- Rate Limiting: When you authenticate a user, you can associate them with a specific usage quota. This can be used to implement rate limiting, preventing any single user from overloading the server with requests.
- Data Accuracy: In many cases, your API's operations will be tied to a specific user's data. For example, a "get user profile" endpoint would need to know which user's profile to retrieve. Authentication provides a way to associate requests with users.
- Audit and Logs: It allows you to keep track of who did what and when. This is very useful when you need to audit the usage of your system.
Implementing express authentication or Node.js authentication is vital for maintaining the integrity, security, and reliable operation of your APIs.
That being said, there might be some API routes that you intentionally leave unauthenticated for various reasons. For instance, a login or registration route needs to be unauthenticated so that users can authenticate or create an account. Similarly, you might provide some public data through your API that doesn't require authentication.
But you should default to authentication. Consider it a component of building as you plan out epics or sprints on APIs. But adding auth doesn’t have to be particularly challenging. You can build this out yourself with middleware functions, but like a lot of elements in authentication, it’s better to use specialized components.
Let’s go through two of these we have at Clerk, ClerkExpressWithAuth()
and ClerkExpressRequireAuth()
to see how we can set these up Express authentication and call these endpoints from a client.
You can check out all the code for this tutorial in this repo.
Authenticating Express API endpoints with Clerk
Before we get to the Clerk specifics, it’s good to define two components of API methods in Express (and elsewhere) that are fundamental concepts to how authentication will work: callbacks and middleware.
A callback function is a function that is passed to another function as a parameter and then invoked by that function at a later time. Callbacks are heavily used in Node.js because it's designed to be asynchronous and non-blocking. When performing I/O operations like making HTTP requests, Node.js can start the operation and then continue executing other code without waiting for the operation to complete. When the operation is complete, the callback function is called with the result.
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. Middleware functions can perform tasks like modifying the request or response objects, ending the request-response cycle, or invoking the next middleware function in the stack.
In Express, middleware functions are often used as callbacks to handle HTTP requests. When you define a route in Express.js, you provide a callback function that's called whenever a client makes a request to that route. This callback function is also a middleware, because it has access to the req
, res
, and next
objects.
So, in this sense, middleware functions are a specific type of callback. They're callbacks that are designed to be used in the context of an HTTP request to an Express.js server.
This is what we’re going to do with Clerk. We’re going to use one of two authentication middleware functions as a callback for the request to our API endpoint. Those two middleware functions are:
ClerkExpressWithAuth()
is a lax authentication middleware that returns an empty auth object when an unauthenticated request is made.ClerkExpressRequireAuth()
is a strict authentication middleware that raises an error when an unauthenticated request is made.
There are subtle but important differences between these two. Let’s go through them.
Using ClerkExpressWithAuth()
ClerkExpressWithAuth()
is lax in that when it fails, it still returns an object, not an error.
Let’s get some code up and running to showcase this function. We’ll create a directory called ‘backend’ and make that the current directory:
With that done, we’ll start installing our dependencies for this code. If you don’t already have it, you’ll also need node as this is the runtime we’re building upon. You can grab the latest build from here.
Then you can run npm init
to create a package.json in that directory. With that we can use npm to install:
- Express, which is the web framework for Node.js we’re going to use. Express is a great option for running node servers because it’s fast, minimal, and unopinionated.
- dotenv, which is the node package you need to read environmental variables in node.
- @clerk/clerk-sdk-node is the Node.js SDK for the Clerk user management platform.
- cors allows us to easily call the endpoint from a client
When they are installed, create a file in your with-auth
directory called app.js
:
Then create a .env
file in the same directory:
This is where you’re going to store your CLERK_API_KEY
. You can find this in your dashboard. Because you are building these routes on the backend, you can use your secret key:
Then open this directory with your IDE. If you are using VS Code, you can just type code .
and you’ll get a window ready in that directory.
Add your secret key to your .env file after CLERK_API_KEY=key-goes-here
. Add the following code to app.js
:
Let’s work through this line by line.
- Firstly we have all our imports:
import "dotenv/config"
imports the "dotenv" package and automatically runs itsconfig
function. This package reads environmental variables from the.env
file and adds them toprocess.env
. Here we need it to access aCLERK_API_KEY
environment variable.- We import
ClerkExpressWithAuth
from the "@clerk/clerk-sdk-node" package. This function is middleware for Express.js that handles authentication with Clerk. - We import
express
from the Express.js package import cors from "cors"
imports the "cors" package, a package used for enabling Cross Origin Resource Sharing (CORS). We’ll need this to aid calling the endpoint from our client.
- We then set a constant
port
to the value of thePORT
environment variable if it's set, otherwise it defaults to3000
. const app = express()
creates a new Express application. The application is what is going to run our server.app.use(cors())
adds the CORS middleware to the Express application, enabling CORS.app.get(…)
defines a route for the path "/protected-endpoint" on the Express app. This route has two middleware functions:ClerkExpressWithAuth
: This is the function that checks the authorization of the incoming request. If the request is authenticated, it setsreq.auth
to an object representing the authenticated user.(req, res) => { res.json(req.auth); console.log(res.json); }
: This is an anonymous function that takes the incoming request and outgoing response as arguments. It sends a JSON response with theauth
object from the request, and then it logs the JSON response function to the console.
- The final
app.listen(…)
part of the code starts the server and makes it listen for incoming connections on the specified port. It logs a message to the console indicating that the server is running and listening on that port.
Let’s run this:
You should now see that message from in your app.listen(…)
terminal:
Great! You have a working endpoint. Let’s call that endpoint (http://localhost:3000/protected-endpoint
) from Postman to see what it returns:
As we said above, ClerkExpressWithAuth()
returns “an empty auth object when unauthenticated.” Now, you have a conundrum—you need an authenticated user to check this really works. To do that, we’ll create a quick React frontend client that calls /protected-endpoint
after authenticating a user.
Keep that app running and open up another terminal. If it opens up in the same directory, make sure you cd .. up a level (you don’t want to create your frontend React app in a subdirectory of your backend—headaches will ensue).
We’ll first install create-react-app to help us (funnily enough) create a react app:
Then run npx create-react-app my-app
where my-app is the name of your app. Here we’ll go with auth-frontend:
Then we’ll cd frontend
to get into that directory and open with our IDE (again using code .
if using VS Code).
Again, we’re going to add Clerk to this project, this time using@clerk/clerk-react
, which is the Clerk React SDK.
We’ll also want to install isomorphic-fetch
and es6-promise
to polyfill the Fetch API for browsers that don't support it:
Like with the backend, you are going to need your Clerk API key. This time though you are going to use your public key as we’re authorizing a frontend client:
Create a .env file and then add that key to it like this: REACT_APP_CLERK_PUBLISHABLE_KEY=key-goes-here
.
Now go to the src/App.js file, remove the boilerplate entirely and add this code:
We won’t go through this line by line because we’ve taken it entirely, with one exception, from our docs on getting started with React. Head there to learn more about Clerk and React.
That one exception is that we’ve swapped out the <Welcome />
component in the documentation within the <SignedIn></SignedIn>
component for an <Auth />
component. Within the src directory add auth.js
and add this code:
We will go through this line by line as it shows how you can pass that authentication token to the API endpoint.
import fetch from "isomorphic-fetch"
imports thefetch
function from theisomorphic-fetch
package.- The next line imports the
React
default export and theuseState
anduseEffect
named exports from thereact
package.useState
is a React Hook that lets you add React state to function components, anduseEffect
lets you perform side effects in function components. - The final import is for the
useAuth
hook from the@clerk/clerk-react
package. This hook provides access to Clerk's auth-related functionality. - The
Auth
function component is then declared. Four pieces of state are created using theuseState
hook:data
,loading
, anderror
for storing API response data, the loading state, and any error messages, respectively. ThegetToken
function is extracted from theuseAuth
hook to allow authentication token retrieval. - We then create a
useEffect
hook to define a side effect that fetches data from an API when the component is first mounted and whenever thegetToken
function changes. - Within the
useEffect
hook, thefetchData
function tries to retrieve a token, then sends a GET request to our/protected-endpoint
. If the request fails, it sets the error state and stops loading. If it succeeds, it sets the data state to the response data, and stops loading.- The critical part of this is the line
Authorization: Bearer ${token}
. Here, we’re passing the authorization token from our now-signed-in-user to our/protected-endpoint
to use in its own authentication.
- The critical part of this is the line
- In the rendering part of the
Auth
component, it checks if theloading
state is true, and if so, returns a "Loading..." message. If there's an error, it displays the error message. Otherwise, it displays the data fetched from/protected-endpoint
. - Finally we export the
Auth
component as the default export of this module. This allows theAuth
component to be imported and used in other parts of the application.
And thus we import that Auth
and call it within the SignedIn
function so we can use the authorization token and pass it to /protected-endpoint
. Within /protected-endpoint
, the ClerkExpressWithAuth middleware will check whether it is a valid token, and, if so, return a full auth object:
Now, on the backend we have our sessionId
and userId
and claims
to use as needed and we know our user is authenticated and allowed to use this API endpoint!
Using ClerkExpressRequireAuth()
ClerkExpressRequireAuth
works slightly different when a user is unauthenticated. Whereas ClerkExpressWithAuth
just returned an empty auth object, ClerkExpressRequireAuth
returns an error.
Swap out your code in your express app.js for this below:
Most of this code is the same as the ClerkExpressWithAuth
example above. The differences are:
- We’re importing the
ClerkExpressRequireAuth
from the Clerk Node.js SDK - We’re calling that
ClerkExpressRequireAuth
middleware within our app.get() function. - We now have an error-handling middleware function. If an error occurs in any middleware function that is run before this one (here the
ClerkExpressRequireAuth
), this function will be called. It logs the stack trace of the error and sends a response with the HTTP status code401
, indicating that the client must authenticate to get the requested response, along with the messageUnauthenticated!
.
And lo and behold, if you call this in Postman you’ll get a 401 and this message:
This is safer as we aren’t even sharing the structure of our auth object.
If we now call this within our React app though, we do get our auth object because we are authenticated:
And with that, you can now authenticate any API endpoint in your Express apps.
Fast, authenticated endpoints
With Express and Clerk you can get authenticated, production-ready APIs up in a matter of just a few minutes. There is more code in our client that for our endpoint. Using ClerkExpressWithAuth()
or ClerkExpressRequireAuth()
you can protect any endpoint and add express authentication or node authentication without the hassle of building it yourself.
This is what you need with authentication—you want it done quickly. That way it’ll actually get done rather than sitting in your backlog queue for months until a dev can get to it. Or an attacker can get to your unprotected endpoints.
Next steps? Check out the code for this tutorial here. Check out Clerk here and more about using Clerk for express and node authentication here, and how to take these security elements even further with the options. Learn how to set up Clerk within your client to make user management super simple here.
Ready to get started?
Sign up today