developers

Announcing the Auth0 Fastify API SDK: Secure Your APIs with Speed!

Secure Fastify APIs easily with the Auth0 Fastify SDK. It simplifies Auth0 integration, offering route protection and token validation.

Apr 10, 20259 min read

Fastify is rapidly gaining traction among Node.js developers, and for good reason! If you're looking for a high-performance and low-overhead way to build web applications and APIs, Fastify is an excellent choice. Its plugin architecture is powerful, but securing those APIs remains a critical concern for developers.

To help address this, we are introducing the

auth0-fastify-api
SDK. This new package brings seamless Auth0 integration into your Fastify projects, simplifying API endpoint protection.

So, What is Fastify, Anyway?

For those who might be new to it, Fastify is a web framework for Node.js focused on delivering a strong developer experience with minimal overhead, built around a robust plugin system. Key features include:

  • High Performance: Fastify is known as one of the fastest Node.js frameworks, utilizing optimizations like schema-based request/response serialization.
  • Extensibility: A rich ecosystem of plugins allows developers to easily add needed functionality.
  • Developer Experience: Features like built-in logging, developer-friendly error handling, and first-class TypeScript support contribute to a smoother development process.
  • Schema-Powered: Use JSON Schema to validate routes and serialize outputs, leading to more predictable and robust applications.
  • Helpful CLI: The
    fastify-cli
    tool streamlines common development tasks like running the app and generating project scaffolds (See fastify-cli on GitHub).
  • NestJS Integration: Fastify's performance can also be leveraged within the popular NestJS framework by using it as the underlying HTTP provider (Learn More).

Introducing
auth0-fastify-api

This new SDK is specifically designed to secure Fastify APIs. It functions as a dedicated Fastify plugin to simplify the validation of access tokens issued by Auth0.

Here's what the SDK offers:

  • Simple Integration: Sets up as a standard Fastify plugin.
  • Automatic Token Validation: Handles the validation of incoming JWT access tokens (sent as Bearer tokens) against your Auth0 tenant configuration.
  • Route Protection: Provides a
    requireAuth
    decorator for easily securing specific routes.
  • Scope Enforcement: Allows specification of required scopes (permissions) needed to access certain endpoints.
  • User Information Access: Makes the validated token payload available on
    request.user
    .
  • Token Access Utility: Includes
    request.getToken()
    for accessing the raw token string if necessary.
  • Standard Error Handling: Sends standards-compliant
    WWW-Authenticate
    headers on authentication/authorization errors.

Getting Started: Quick Setup

Integrating the SDK involves two main steps:

  1. Install the SDK:
npm install @auth0/auth0-fastify-api
# or
yarn add @auth0/auth0-fastify-api
  1. Register the Plugin:

In your main Fastify application file, register the plugin, providing your Auth0 domain and API audience:

import Fastify from "fastify";
import Auth0 from "@auth0/auth0-fastify-api";

const fastify = Fastify({ logger: true });

fastify.register(Auth0, {
  domain: "YOUR_AUTH0_DOMAIN", // For example: your-tenant.auth0.com
  audience: "YOUR_API_AUDIENCE", // The Identifier of your API in Auth0
});

// ... rest of your setup

Remember to replace the placeholder values with your actual Auth0 domain and API identifier.

A Functional Example

Let's look at a functional example that demonstrates setting up a basic server, loading configuration from the environment, and protecting a route.

Create a

fastify
directory anywhere in your system and initialize a Node.js project within it:

npm init -y

Within that directory, create a

server.ts
file and follow the instructions in this section to build it up.

First, import the necessary modules and use

dotenv
to load configurations like your Auth0 domain and audience from a
.env
file, which avoids hardcoding sensitive credentials.

import Fastify from "fastify";
import Auth0 from "@auth0/auth0-fastify-api";
import * as dotenv from "dotenv";

dotenv.config();

const fastify = Fastify({
  logger: true,
});

if (!process.env.AUTH0_DOMAIN || !process.env.AUTH0_AUDIENCE) {
  fastify.log.error("Auth0 domain or audience not configured in .env file");

  process.exit(1);
}

It's good to check that the environment variables were loaded correctly.

Next, register the

Auth0
plugin, passing the configuration loaded from the environment variables:

fastify.register(Auth0, {
  domain: process.env.AUTH0_DOMAIN,
  audience: process.env.AUTH0_AUDIENCE,
});

To ensure the

Auth0
plugin has finished loading before defining routes that use its decorators, such as
requireAuth
, register your protected routes inside an anonymous plugin function:

fastify.register(() => {
  fastify.get("/", { preHandler: fastify.requireAuth() }, (_, reply) => {
    reply.send({ ok: true });
  });
});

Finally, start the server, listening on the port

3000
:

fastify.listen({ port: 3000 }, function (err, address) {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }

  fastify.log.info(`Server is now listening on ${address}`);
});

You can run this short example by following these steps:

Install dependencies:

npm install fastify @auth0/auth0-fastify-api dotenv

Create a

.env
file with your Auth0 details:

AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_AUDIENCE=your-api-identifier

You can follow the steps on the "Register APIs" document to learn how to create an API in the Auth0 Dashboard and get the required values: the Auth0 Domain, which is your tenant domain, and the Auth0 Audience, which is a unique identifier for your API.

Run the server:

npx ts-node server.ts

This provides a basic but functional starting point that loads configuration securely and correctly waits for the Auth0 plugin before defining protected routes.

Securing A Route with
requireAuth

As you saw earlier, to protect an individual API endpoint, use the

requireAuth
decorator provided by the SDK. Add it as a
preHandler
in your route definition:

fastify.get(
  "/private",
  { preHandler: fastify.requireAuth() },
  async (request, reply) => {
    const user = request.user;
    return { message: "This is a protected route", user };
  }
);

Fastify will only reach the body of the route event handler if the token sent in the request authorization header is valid. The decoded token claims of the token are available in the

request.user
object.

If a request is made to

/private
without a valid Bearer token in the
Authorization
header, the SDK automatically rejects it with a
401 Unauthorized
status.

Securing Multiple Routes

Applying the

preHandler
to every route can be repetitive if multiple routes require the same protection. Fastify's plugin system and encapsulation offer an efficient way to apply an authorization guard at the router level to protect multiple routes at once.

Group related routes within their own plugin and apply the

requireAuth
hook once using
fastify.addHook
within that plugin's context. All routes defined within that plugin will then inherit the hook.

Let's see this concept in action.

1. Create a routes plugin:

Within an asynchronous Fastify plugin, you use

fastify.addHook("preHandler", fastify.requireAuth())
to run the authentication check before any route defined within this plugin. Routes defined after this hook (
/profile
,
/orders
, etc.) are automatically protected.

// private-routes.ts
import { FastifyPluginAsync } from "fastify";

const privateRoutes: FastifyPluginAsync = async (fastify, opts) => {
  // Apply the hook once for all routes in this plugin
  fastify.addHook("preHandler", fastify.requireAuth());

  fastify.get("/profile", async (request, reply) => {
    // Token is already validated by the hook
    return { user: request.user };
  });

  fastify.get("/orders", async (request, reply) => {
    // Hook handles main auth; specific checks can still happen here
    if (!request.user.scope?.includes("read:orders")) {
      return reply.code(403).send({ error: "insufficient_scope" });
    }
    return {
      orders: [
        /* ... */
      ],
      user_id: request.user.sub,
    };
  });
  // Define more protected routes here...
};

export default privateRoutes;

2. Register the plugin in your main server file:

In your main server setup (likely within the

fastify.register(async function(fastify){...})
block), import the
privateRoutes
plugin. Use
fastify.register
to load it, passing the
prefix
option (e.g.,
{ prefix: "/api/v1" }
) to mount all routes from the plugin under that path.

import Fastify from "fastify";
import Auth0 from "@auth0/auth0-fastify-api";
import * as dotenv from "dotenv";
import privateRoutes from "./private-routes"; // Import the routes plugin

dotenv.config();

const fastify = Fastify();

fastify.register(Auth0, {
  domain: process.env.AUTH0_DOMAIN,
  audience: process.env.AUTH0_AUDIENCE,
});

fastify.register(async function (fastify) {
  if (!process.env.AUTH0_DOMAIN || !process.env.AUTH0_AUDIENCE) {
    fastify.log.error("Auth0 domain or audience not configured in .env file");
    
    process.exit(1);
  }

  // Register the protected routes plugin under the /api/v1 prefix
  fastify.register(privateRoutes, { prefix: "/api/v1" });

  // Routes defined outside that plugin don't inherit its hooks 
  // and need their own protection
  fastify.get("/", { preHandler: fastify.requireAuth() }, (_, reply) => {
    reply.send({ ok: true });
  });
});

fastify.listen({ port: process.env.PORT || 3000 });

Now, requests to

/api/v1/profile
,
/api/v1/orders
, or any other route inside
private-routes.ts
are automatically protected by the
requireAuth
hook applied at the plugin level.

Enforcing Scopes

If you need to ensure the user has been granted specific permissions (scopes) in their access token, use the

scopes
property of the
requireAuth
decorator:

const requiredScopes = ["read:messages", "write:messages"];

fastify.post(
  "/messages",
  { preHandler: fastify.requireAuth({ scopes: requiredScopes }) },
  async (request, reply) => {
    return { message: "Message created successfully", user: request.user };
  }
);

Only requests with valid tokens containing both

read:messages
and
write:messages
scopes will reach the route event handler body.

If you don't have multiple scopes to check, you can also assign

scopes
a string as its value. For a single scope, you could use the following:

const requiredScopes = "read:messages";

If the validated token doesn't include all the specified scopes, the SDK will return a

403 Forbidden
error with an
insufficient_scope
code.

Accessing User Information

Once a token is validated by

requireAuth
, its decoded payload (claims) is attached to the Fastify request object at
request.user
. This typically includes information like the user ID (
sub
), audience (
aud
), issuer (
iss
), and granted scopes (
scope
).

fastify.get(
  "/profile",
  { preHandler: fastify.requireAuth() },
  async (request, reply) => {
    // Access the user's unique identifier (subject)
    const userId = request.user.sub;
    // Access the scopes granted in this token
    const scopes = request.user.scope;

    return { userId, scopes, fullProfile: request.user };
  }
);

Conclusion

The

auth0-fastify-api
SDK provides a straightforward way to secure your Fastify APIs by integrating Auth0 token validation. It offers simple decorators for protecting routes and checking scopes, allowing you to focus on your application's core logic.

We encourage you to try it out in your next Fastify project and share your feedback! Check out the source code and examples to learn more.

Happy coding!