> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brainstormer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication Pattern

> JWT auth pattern, middleware implementation, and do's and don'ts for the Brainstormer V2 authentication system.

## Overview

All authenticated API routes in Brainstormer V2 follow a specific JWT pattern:

1. **JWT Authentication Middleware** extracts user info from Bearer token
2. Middleware sets `request.user` with userId, organizationId, email
3. Route handlers access `request.user.organizationId` instead of reading headers directly
4. The **gateway verifies the JWT signature** with `verifyJwt(token, JWT_SECRET)`; downstream services (4001–4006, not publicly exposed) trust gateway-forwarded headers and do not re-verify

## What NOT to Do

<Warning>
  These anti-patterns will cause authentication failures. Never use them.
</Warning>

### Never Read Headers Directly

```typescript theme={null}
// INCORRECT - This will cause "Missing organization context" errors
const organizationId = request.headers["organization-id"] as string;
const userId = request.headers["user-id"] as string;

if (!organizationId) {
  return reply.status(401).send({
    error: "Unauthorized",
    message: "Missing organization context",
  });
}
```

**Why this fails:** The frontend never sets `organization-id` or `user-id` headers. These headers don't exist in the request, so authentication will always fail with 401 errors.

### Gateway Verifies, Downstream Services Trust Headers

```typescript theme={null}
// At the GATEWAY (the only public ingress) — verify the signature:
const payload = verifyJwt(token, config.JWT_SECRET) as JWTPayload;
```

**Why:** The gateway MUST verify the JWT signature with `verifyJwt(token, JWT_SECRET)` (HMAC-SHA256). Only downstream services (ports 4001–4006) skip re-verification — they trust the auth context the gateway forwards as headers, and must never be exposed to the public internet.

## Correct Pattern

### Step 1: Add JWT Authentication Middleware

```typescript theme={null}
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";

interface AuthenticatedRequest extends FastifyRequest {
  user?: {
    id: string;
    organizationId: string;
    email: string;
  };
}

interface JWTPayload {
  userId: string;
  email: string;
  organizations: Array<{
    id: string;
    name: string;
    slug: string;
    role: string;
  }>;
  iat: number;
  exp: number;
}

export async function yourRoutes(fastify: FastifyInstance) {
  // JWT Authentication Middleware
  fastify.addHook(
    "preHandler",
    async (request: AuthenticatedRequest, reply: FastifyReply) => {
      // Skip authentication for public routes
      if (
        request.url.startsWith("/public/") ||
        request.url.startsWith("/health")
      ) {
        return;
      }

      try {
        const authHeader = request.headers.authorization;
        if (!authHeader || !authHeader.startsWith("Bearer ")) {
          return reply
            .status(401)
            .send({ error: "Missing or invalid authorization header" });
        }

        const token = authHeader.substring(7);

        // Downstream service: read the payload forwarded by the gateway
        // (the gateway has already verified the signature)
        const payload = JSON.parse(
          Buffer.from(token.split(".")[1], "base64").toString(),
        ) as JWTPayload;

        if (
          !payload.userId ||
          !payload.organizations ||
          payload.organizations.length === 0
        ) {
          return reply.status(401).send({ error: "Invalid token payload" });
        }

        // Use the first organization (primary org)
        const primaryOrg = payload.organizations[0];

        request.user = {
          id: payload.userId,
          organizationId: primaryOrg.id,
          email: payload.email,
        };
      } catch (error) {
        return reply.status(401).send({ error: "Invalid authorization token" });
      }
    },
  );

  // Your routes go here...
}
```

### Step 2: Access User Info via request.user

```typescript theme={null}
fastify.post(
  "/your-endpoint/:id",
  async (
    request: AuthenticatedRequest<{
      Params: { id: string };
      Body: YourBodyType;
    }>,
    reply: FastifyReply,
  ) => {
    const { id } = request.params;
    const userId = request.user!.id;
    const organizationId = request.user!.organizationId;
    const email = request.user!.email;

    // Use organizationId and userId for queries...
  },
);
```

### Step 3: Skip Authentication for Public Routes

```typescript theme={null}
if (
  request.url.startsWith("/public/") ||
  request.url.startsWith("/health") ||
  request.url.startsWith("/check-slug/")
) {
  return; // Skip authentication
}
```

## Why the Gateway Verifies and Downstream Services Trust Headers

<AccordionGroup>
  <Accordion title="Gateway Responsibility">
    API Gateway (Port 4000) is the only public ingress and verifies JWT signatures with `verifyJwt(token, JWT_SECRET)` before routing to services.
  </Accordion>

  <Accordion title="Trust Boundary">
    Services trust requests from the gateway because service ports (4001–4006) are not exposed to the public internet — only the gateway is.
  </Accordion>

  <Accordion title="Performance">
    Avoids repeated signature verification across services once the gateway has verified.
  </Accordion>

  <Accordion title="Simplicity">
    Downstream services only read the gateway-forwarded headers, not manage secrets.
  </Accordion>
</AccordionGroup>

## Organization Selection

```typescript theme={null}
// Use the first organization as primary org
const primaryOrg = payload.organizations[0];
```

Users can belong to multiple organizations. The primary org is always first in the JWT payload.

## Common Errors and Solutions

| Error                                                | Cause                                                                                       | Solution                                                                                                    |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| "Missing organization context" (401)                 | Route handler reading `request.headers["organization-id"]`                                  | Use JWT middleware, access `request.user!.organizationId`                                                   |
| "Invalid authorization token" (401)                  | Gateway failing to verify, or a downstream service re-verifying instead of trusting headers | Gateway must verify with `verifyJwt(token, JWT_SECRET)`; downstream services read gateway-forwarded headers |
| "Cannot read property 'organizationId' of undefined" | Forgetting JWT middleware                                                                   | Add `fastify.addHook("preHandler", ...)` before routes                                                      |

## Reference Implementation

```typescript theme={null}
// Add middleware (once per route file)
fastify.addHook("preHandler", async (request: AuthenticatedRequest, reply) => {
  if (request.url.startsWith("/public/")) return;

  const authHeader = request.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return reply.status(401).send({ error: "Missing authorization header" });
  }

  const token = authHeader.substring(7);
  const payload = JSON.parse(
    Buffer.from(token.split(".")[1], "base64").toString()
  ) as JWTPayload;

  request.user = {
    id: payload.userId,
    organizationId: payload.organizations[0].id,
    email: payload.email,
  };
});

// Route handler
fastify.post("/endpoint/:id", async (request: AuthenticatedRequest, reply) => {
  const organizationId = request.user!.organizationId;
  const userId = request.user!.id;
});
```

## Testing Checklist

* [ ] Added JWT authentication middleware with `fastify.addHook("preHandler", ...)`
* [ ] Defined `AuthenticatedRequest` and `JWTPayload` interfaces
* [ ] Using `request.user!.organizationId` instead of reading headers
* [ ] Public routes properly skip authentication
* [ ] Tested with valid JWT token (should succeed)
* [ ] Tested with missing Bearer token (should return 401)
* [ ] Tested with invalid JWT format (should return 401)

**Canonical reference implementation:** `/services/bot/src/routes/bots.routes.ts`
