Sign in

Protect API Routes

This guide shows how to protect Fastify API routes using protectApi() from the MonoCloud Backend Node SDK.

You can protect routes globally, per-route, or combine both approaches with scope and group-based authorization.

What you'll cover

  • Protect all routes globally
  • Protect individual routes
  • Require specific scopes
  • Require specific groups
  • Read claims from the authenticated token

Before you begin

This guide assumes you've completed the Fastify quickstart or the installation guide.

You should already have:

  • A Fastify project
  • The @monocloud/backend-node SDK installed
  • Environment variables configured in .env

Protect all routes globally

Apply protectApi() as an application-level hook to protect all routes.

src/server.ts
import "dotenv/config";
import Fastify from "fastify";
import {
  protectApi,
  type AuthenticatedFastifyRequest,
} from "@monocloud/backend-node/fastify";

const app = Fastify();

const protect = protectApi();

// All routes require a valid access token
app.addHook("onRequest", protect());

app.get("/api/data", async (request) => {
  const { claims } = request as AuthenticatedFastifyRequest;
  return { claims };
});

app.listen({ port: 3000 });

How it works:

  • protectApi() creates a protection factory from environment variables
  • protect() returns a Fastify onRequest hook that validates the Bearer token
  • Every request must include a valid Authorization: Bearer <token> header
  • Validated claims are attached to request.claims

Protect individual routes

Apply the hook to specific routes using the onRequest route option.

src/server.ts
import "dotenv/config";
import Fastify from "fastify";
import {
  protectApi,
  type AuthenticatedFastifyRequest,
} from "@monocloud/backend-node/fastify";

const app = Fastify();

const protect = protectApi();

// Public route — no token required
app.get("/api/public", async () => {
  return { message: "Public data" };
});

// Protected route — requires a valid access token
app.get("/api/protected", { onRequest: protect() }, async (request) => {
  const { claims } = request as AuthenticatedFastifyRequest;
  return { claims };
});

app.listen({ port: 3000 });

Require specific scopes

Pass scopes to protect() to require specific access token scopes.

ts
app.get(
  "/api/admin",
  { onRequest: protect({ scopes: ["write"] }) },
  async (request) => {
    const { claims } = request as AuthenticatedFastifyRequest;
    return { claims };
  }
);

Behavior:

  • Returns 401 Unauthorized if the token is missing or invalid
  • Returns 403 Forbidden if the token is valid but lacks the required scopes
  • Executes the handler if the token contains all required scopes

Require specific groups

Pass groups to protect() to require specific group memberships.

ts
app.get(
  "/api/team",
  { onRequest: protect({ groups: ["engineering"] }) },
  async (request) => {
    const { claims } = request as AuthenticatedFastifyRequest;
    return { claims };
  }
);

Behavior:

  • Returns 401 Unauthorized if the token is missing or invalid
  • Returns 403 Forbidden if the token is valid but the user is not in the required group
  • Executes the handler if the user belongs to any of the specified groups

Combine global and per-route protection

You can apply global token validation and add scope or group requirements to specific routes.

src/server.ts
import "dotenv/config";
import Fastify from "fastify";
import {
  protectApi,
  type AuthenticatedFastifyRequest,
} from "@monocloud/backend-node/fastify";

const app = Fastify();

const protect = protectApi();

// All routes require a valid access token
app.addHook("onRequest", protect());

// Any authenticated user can access this route
app.get("/api/data", async (request) => {
  const { claims } = request as AuthenticatedFastifyRequest;
  return { claims };
});

// Only users with the write scope and in the engineering group can access this route
app.get(
  "/api/admin",
  { onRequest: protect({ scopes: ["write"], groups: ["engineering"] }) },
  async (request) => {
    const { claims } = request as AuthenticatedFastifyRequest;
    return { claims };
  }
);

app.listen({ port: 3000 });

Response behavior

ScenarioStatus codeResponse
Missing or invalid token401{ "message": "unauthorized" }
Valid token, missing scopes403{ "message": "forbidden" }
Valid token, missing groups403{ "message": "forbidden" }
Valid token, authorizedRoute handler executes normally
© 2024 MonoCloud. All rights reserved.