> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> This guide demonstrates how to protect Fastify API endpoints using JWT access tokens with the Auth0 Fastify API SDK.

# Protect Your Fastify API

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

<HowToSchema />

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 API authentication automatically in minutes using [agent skills](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-fastify-api
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  Add Auth0 JWT authentication to my Fastify API
  ```

  Your AI assistant will automatically create your Auth0 API, fetch credentials, install `@auth0/auth0-fastify-api`, configure the plugin, and protect your API endpoints with JWT validation. [Full agent skills documentation →](/docs/quickstart/agent-skills)
</Accordion>

<Note>
  **Prerequisites:** Before you begin, ensure you have the following installed:

  * **[Node.js](https://nodejs.org/en/download)** 20 LTS or newer
  * **[npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)** 10+ or **[yarn](https://classic.yarnpkg.com/lang/en/docs/install/)** 1.22+ or **[pnpm](https://pnpm.io/installation)** 8+

  Verify installation: `node --version && npm --version`

  **Fastify Version Compatibility:** This quickstart works with **Fastify 5.x** and newer.
</Note>

## Get Started

This quickstart demonstrates how to protect Fastify API endpoints using JWT access tokens. You'll build a secure API that validates Auth0 access tokens and grants access to protected resources.

<Steps>
  <Step title="Create a new project" stepNumber={1}>
    Create a new directory for your Fastify API and initialize a Node.js project.

    ```shellscript theme={null}
    mkdir auth0-fastify-api && cd auth0-fastify-api
    ```

    Initialize the project

    ```shellscript theme={null}
    npm init -y
    ```

    Create the project structure

    ```shellscript theme={null}
    touch server.js .env
    ```
  </Step>

  <Step title="Install the Auth0 Fastify API SDK" stepNumber={2}>
    Install the required dependencies

    ```shellscript theme={null}
    npm install @auth0/auth0-fastify-api fastify dotenv
    ```

    Update your `package.json` to add start scripts:

    ```json package.json theme={null}
    {
      "name": "auth0-fastify-api",
      "version": "1.0.0",
      "type": "module",
      "main": "server.js",
      "scripts": {
        "start": "node server.js",
        "dev": "node --watch server.js"
      },
      "dependencies": {
        "@auth0/auth0-fastify-api": "^1.2.0",
        "dotenv": "^16.3.1",
        "fastify": "^5.0.0"
      }
    }
    ```
  </Step>

  <Step title="Setup your Auth0 API" stepNumber={3}>
    Next, you need to create a new API on your Auth0 tenant and add the environment variables to your project.

    You have two options to set up your Auth0 API: use a CLI command or configure manually via the Dashboard:

    <Tabs>
      <Tab title="CLI">
        Run the following command in your project's root directory to create an Auth0 API:

        <CodeGroup>
          ```shellscript Mac theme={null}
          # Install Auth0 CLI (if not already installed)
          brew tap auth0/auth0-cli && brew install auth0

          # Create Auth0 API
          auth0 apis create \
            --name "My Fastify API" \
            --identifier https://my-fastify-api.example.com
          ```

          ```powershell Windows theme={null}
          # Install Auth0 CLI (if not already installed)
          scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
          scoop install auth0

          # Create Auth0 API
          auth0 apis create `
            --name "My Fastify API" `
            --identifier https://my-fastify-api.example.com
          ```
        </CodeGroup>

        After creation, copy the **Identifier** and your **Domain** values, then create your `.env` file:

        ```bash .env theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
        AUTH0_AUDIENCE=YOUR_API_IDENTIFIER
        ```

        <Note>
          This command will:

          1. Check if you're authenticated (and prompt for login if needed)
          2. Create an Auth0 API with the specified identifier
          3. Display the API details including the domain and identifier
        </Note>
      </Tab>

      <Tab title="Dashboard">
        1. Go to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
        2. Navigate to **Applications** → **APIs** → **Create API**
        3. Enter a name for your API (e.g., "My Fastify API")
        4. Set the **Identifier** (e.g., `https://my-fastify-api.example.com`)
           * This is your API audience and must be a valid URL format
           * It doesn't need to be a real URL - it's just an identifier
        5. Keep **Signing Algorithm** as **RS256**
        6. Click **Create**
        7. Copy the **Identifier** value from the **Settings** tab

        Create your `.env` file with the following values:

        ```bash .env theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
        AUTH0_AUDIENCE=YOUR_API_IDENTIFIER
        ```

        <Warning>
          Replace `YOUR_AUTH0_DOMAIN` with your Auth0 tenant domain (e.g., `dev-abc123.us.auth0.com`) and `YOUR_API_IDENTIFIER` with your API identifier from the dashboard (e.g., `https://my-fastify-api.example.com`).
        </Warning>
      </Tab>
    </Tabs>

    <Tip>
      Verify your `.env` file exists: `cat .env` (Mac/Linux) or `type .env` (Windows)
    </Tip>
  </Step>

  <Step title="Configure the Auth0 API plugin" stepNumber={4}>
    Create your Fastify server and register the Auth0 API plugin:

    ```javascript server.js {1-3,6-7,10-13,16-17} lines theme={null}
    import 'dotenv/config';
    import Fastify from 'fastify';
    import fastifyAuth0Api from '@auth0/auth0-fastify-api';

    const fastify = Fastify({ logger: true });
    const port = process.env.PORT || 3001;

    // Register Auth0 API plugin
    await fastify.register(fastifyAuth0Api, {
      domain: process.env.AUTH0_DOMAIN,
      audience: process.env.AUTH0_AUDIENCE,
    });

    // Start server
    fastify.listen({ port }, (err) => {
      if (err) {
        fastify.log.error(err);
        process.exit(1);
      }
      fastify.log.info(`API server running at http://localhost:${port}`);
    });
    ```

    **What this does:**

    * Registers the Auth0 API plugin with your Auth0 domain and API audience
    * Configures JWT validation for incoming requests
    * Makes the `requireAuth()` preHandler available for protecting routes
  </Step>

  <Step title="Create API routes" stepNumber={5}>
    Add public and protected routes to your `server.js`:

    ```javascript server.js expandable lines theme={null}
    import 'dotenv/config';
    import Fastify from 'fastify';
    import fastifyAuth0Api from '@auth0/auth0-fastify-api';

    const fastify = Fastify({ logger: true });
    const port = process.env.PORT || 3001;

    // Register Auth0 API plugin
    await fastify.register(fastifyAuth0Api, {
      domain: process.env.AUTH0_DOMAIN,
      audience: process.env.AUTH0_AUDIENCE,
    });

    // Public route - no authentication required
    fastify.get('/api/public', async (request, reply) => {
      return {
        message: 'Hello from a public endpoint! You don\'t need to be authenticated to see this.',
        timestamp: new Date().toISOString(),
      };
    });

    // Protected route - requires valid access token
    fastify.get('/api/private', {
      preHandler: fastify.requireAuth()
    }, async (request, reply) => {
      return {
        message: 'Hello from a protected endpoint! You successfully authenticated.',
        user: request.user.sub,
        timestamp: new Date().toISOString(),
      };
    });

    // Protected route - returns user information from token
    fastify.get('/api/profile', {
      preHandler: fastify.requireAuth()
    }, async (request, reply) => {
      return {
        message: 'Your user profile from the access token',
        profile: request.user,
      };
    });

    // Start server
    fastify.listen({ port }, (err) => {
      if (err) {
        fastify.log.error(err);
        process.exit(1);
      }
      fastify.log.info(`API server running at http://localhost:${port}`);
    });
    ```

    **Key points:**

    * Public routes don't require authentication
    * Protected routes use `preHandler: fastify.requireAuth()` to require a valid JWT
    * `request.user` contains the decoded JWT claims for authenticated requests
    * The `sub` claim contains the user's unique identifier
  </Step>

  <Step title="Run your API" stepNumber={6}>
    Start the development server:

    ```shellscript theme={null}
    npm run dev
    ```

    Your API is now running at [http://localhost:3001](http://localhost:3001).

    <Info>
      The `--watch` flag in Node.js 20+ automatically restarts the server when files change.
    </Info>
  </Step>

  <Step title="Test your API" stepNumber={7}>
    Test the public endpoint (no authentication required):

    ```bash theme={null}
    curl http://localhost:3001/api/public
    ```

    You should see:

    ```json theme={null}
    {
      "message": "Hello from a public endpoint! You don't need to be authenticated to see this.",
      "timestamp": "2024-01-15T10:30:00.000Z"
    }
    ```

    Test the protected endpoint without a token (should fail):

    ```bash theme={null}
    curl http://localhost:3001/api/private
    ```

    You should see a 401 Unauthorized error:

    ```json theme={null}
    {
      "error": "Unauthorized",
      "message": "No authorization token was found"
    }
    ```

    To test with a valid token, you need to:

    1. Create a client application (web or mobile app) that authenticates users
    2. Configure the client to request an access token for your API (using the audience parameter)
    3. Use that access token in the Authorization header

    Example with a token:

    ```bash theme={null}
    curl http://localhost:3001/api/private \
      -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
    ```
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You should now have a protected API. Your API:

  1. Accepts requests to public endpoints without authentication
  2. Rejects requests to protected endpoints without a valid token
  3. Validates JWT tokens against your Auth0 domain and audience
  4. Provides user information from the token claims via `request.user`
</Check>

***

## Advanced Usage

<Accordion title="Custom Token Claims with TypeScript">
  Extend the Token interface to add type safety for custom claims in your access tokens:

  ```typescript server.ts theme={null}
  import '@auth0/auth0-fastify-api';

  // Extend the Token interface with your custom claims
  declare module '@auth0/auth0-fastify-api' {
    interface Token {
      sub: string;
      permissions?: string[];
      'https://myapp.com/roles'?: string[];
      email?: string;
      email_verified?: boolean;
    }
  }
  ```

  Now TypeScript will recognize your custom claims:

  ```typescript server.ts theme={null}
  fastify.get('/api/profile', {
    preHandler: fastify.requireAuth()
  }, async (request, reply) => {
    // TypeScript knows about these properties
    const userRoles = request.user['https://myapp.com/roles']; // string[] | undefined
    const permissions = request.user.permissions; // string[] | undefined
    const email = request.user.email; // string | undefined

    return {
      userId: request.user.sub,
      roles: userRoles || [],
      permissions: permissions || [],
      email: email,
    };
  });
  ```

  <Note>
    Custom claims must use namespaced URLs (e.g., `https://myapp.com/roles`) unless they're standard OIDC claims. [Learn more about custom claims](https://auth0.com/docs/secure/tokens/json-web-tokens/create-custom-claims).
  </Note>
</Accordion>

<Accordion title="Permission-Based Authorization">
  Check for specific permissions in the access token:

  ```javascript server.js theme={null}
  // Middleware to check for specific permission
  function requirePermission(permission) {
    return async (request, reply) => {
      const permissions = request.user.permissions || [];

      if (!permissions.includes(permission)) {
        return reply.status(403).send({
          error: 'Forbidden',
          message: `Missing required permission: ${permission}`
        });
      }
    };
  }

  // Route requiring 'read:messages' permission
  fastify.get('/api/messages', {
    preHandler: [
      fastify.requireAuth(),
      requirePermission('read:messages')
    ]
  }, async (request, reply) => {
    return {
      messages: ['Message 1', 'Message 2', 'Message 3']
    };
  });

  // Route requiring 'write:messages' permission
  fastify.post('/api/messages', {
    preHandler: [
      fastify.requireAuth(),
      requirePermission('write:messages')
    ]
  }, async (request, reply) => {
    return {
      message: 'Message created successfully',
      id: 'msg_123'
    };
  });
  ```

  <Note>
    Permissions must be configured in your Auth0 API settings and granted to clients. [Learn more about API permissions](https://auth0.com/docs/manage-users/access-control/configure-core-rbac/rbac-users/assign-permissions-users).
  </Note>
</Accordion>

<Accordion title="Role-Based Authorization">
  Implement role-based access control using custom claims:

  ```javascript server.js theme={null}
  // Middleware to check for specific role
  function requireRole(role) {
    return async (request, reply) => {
      const roles = request.user['https://myapp.com/roles'] || [];

      if (!roles.includes(role)) {
        return reply.status(403).send({
          error: 'Forbidden',
          message: `Missing required role: ${role}`
        });
      }
    };
  }

  // Admin-only route
  fastify.get('/api/admin/users', {
    preHandler: [
      fastify.requireAuth(),
      requireRole('admin')
    ]
  }, async (request, reply) => {
    return {
      users: [
        { id: 1, name: 'User 1' },
        { id: 2, name: 'User 2' }
      ]
    };
  });

  // Manager or admin route
  function requireAnyRole(...roles) {
    return async (request, reply) => {
      const userRoles = request.user['https://myapp.com/roles'] || [];
      const hasRole = roles.some(role => userRoles.includes(role));

      if (!hasRole) {
        return reply.status(403).send({
          error: 'Forbidden',
          message: `Missing required role. Need one of: ${roles.join(', ')}`
        });
      }
    };
  }

  fastify.get('/api/reports', {
    preHandler: [
      fastify.requireAuth(),
      requireAnyRole('admin', 'manager')
    ]
  }, async (request, reply) => {
    return { reports: [] };
  });
  ```

  <Note>
    Roles must be added to tokens using Auth0 Actions. [Learn how to add roles to tokens](https://auth0.com/docs/customize/actions/flows-and-triggers/login-flow/add-user-roles-to-id-and-access-tokens).
  </Note>
</Accordion>

<Accordion title="CORS Configuration">
  Enable CORS to allow requests from web applications:

  ```bash theme={null}
  npm install @fastify/cors
  ```

  ```javascript server.js theme={null}
  import cors from '@fastify/cors';

  await fastify.register(cors, {
    origin: ['http://localhost:3000', 'http://localhost:5173'], // Your web app URLs
    credentials: true,
  });
  ```

  For production, specify exact origins:

  ```javascript server.js theme={null}
  await fastify.register(cors, {
    origin: [
      'https://myapp.com',
      'https://www.myapp.com'
    ],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
  });
  ```
</Accordion>

<Accordion title="Error Handling">
  Add comprehensive error handling for authentication errors:

  ```javascript server.js theme={null}
  // Custom error handler
  fastify.setErrorHandler((error, request, reply) => {
    fastify.log.error(error);

    // Handle JWT validation errors
    if (error.statusCode === 401) {
      return reply.status(401).send({
        error: 'Unauthorized',
        message: error.message || 'Invalid or missing access token',
        code: 'UNAUTHORIZED'
      });
    }

    // Handle permission/role errors
    if (error.statusCode === 403) {
      return reply.status(403).send({
        error: 'Forbidden',
        message: error.message || 'Insufficient permissions',
        code: 'FORBIDDEN'
      });
    }

    // Handle other errors
    return reply.status(error.statusCode || 500).send({
      error: 'Internal Server Error',
      message: 'An unexpected error occurred',
      code: 'INTERNAL_ERROR'
    });
  });

  // Not found handler
  fastify.setNotFoundHandler((request, reply) => {
    return reply.status(404).send({
      error: 'Not Found',
      message: `Route ${request.method} ${request.url} not found`,
      code: 'NOT_FOUND'
    });
  });
  ```
</Accordion>

<Accordion title="Rate Limiting">
  Protect your API from abuse with rate limiting:

  ```bash theme={null}
  npm install @fastify/rate-limit
  ```

  ```javascript server.js theme={null}
  import rateLimit from '@fastify/rate-limit';

  await fastify.register(rateLimit, {
    max: 100, // Maximum requests
    timeWindow: '1 minute', // Time window
    errorResponseBuilder: (request, context) => {
      return {
        error: 'Too Many Requests',
        message: `Rate limit exceeded. Try again in ${context.after}`,
        retryAfter: context.after
      };
    }
  });

  // Apply stricter limits to specific routes
  fastify.get('/api/expensive-operation', {
    preHandler: fastify.requireAuth(),
    config: {
      rateLimit: {
        max: 10,
        timeWindow: '1 minute'
      }
    }
  }, async (request, reply) => {
    return { result: 'expensive operation result' };
  });
  ```
</Accordion>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Common Issues and Solutions">
    ### "No authorization token was found"

    **Problem:** The API cannot find the access token in the request.

    **Solutions:**

    1. Ensure the `Authorization` header is present: `Authorization: Bearer YOUR_TOKEN`
    2. Check that "Bearer" is included before the token
    3. Verify the token is not expired

    ### "Invalid token" or "jwt malformed"

    **Problem:** The token format is invalid.

    **Solutions:**

    1. Ensure you're using an **access token**, not an ID token
    2. The token should be obtained with your API's `audience` parameter
    3. Check that the token is a valid JWT (should have three parts separated by dots)

    ### "Invalid signature"

    **Problem:** The token signature doesn't match.

    **Solutions:**

    1. Verify `AUTH0_DOMAIN` matches the domain that issued the token
    2. Ensure you're using RS256 signing algorithm (default)
    3. Check that the token hasn't been modified

    ### "Invalid audience"

    **Problem:** The token's audience doesn't match your API.

    **Solution:** The client application must request a token with the correct audience:

    ```javascript theme={null}
    // In your client app
    const token = await getAccessTokenSilently({
      authorizationParams: {
        audience: 'https://my-fastify-api.example.com' // Must match your API identifier
      }
    });
    ```

    ### CORS errors in browser

    **Problem:** Browser blocks API requests due to CORS policy.

    **Solution:** Install and configure `@fastify/cors`:

    ```bash theme={null}
    npm install @fastify/cors
    ```

    ```javascript theme={null}
    import cors from '@fastify/cors';

    await fastify.register(cors, {
      origin: 'http://localhost:3000', // Your frontend URL
      credentials: true
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

Now that you have a protected API, consider exploring:

* **[Fastify Web App Quickstart](/docs/quickstart/webapp/fastify)** - Build a web application that calls your API
* **[Role-Based Access Control](https://auth0.com/docs/manage-users/access-control/rbac)** - Implement fine-grained permissions
* **[API Authorization Best Practices](https://auth0.com/docs/secure/tokens/access-tokens)** - Learn about access token best practices
* **[Monitor Your API](https://auth0.com/docs/deploy-monitor/logs)** - Set up logging and monitoring

***

## Resources

* **[auth0-fastify-api GitHub](https://github.com/auth0/auth0-fastify/tree/main/packages/auth0-fastify-api)** - Source code and examples
* **[Fastify Documentation](https://fastify.dev/)** - Learn more about Fastify
* **[Auth0 API Authentication](https://auth0.com/docs/secure/tokens/access-tokens)** - Understanding access tokens
* **[Auth0 Community](https://community.auth0.com/)** - Get help from the community
