CrewAI Tutorial (TypeScript): adding authentication for beginners

By Cyprian AaronsUpdated 2026-04-21
crewaiadding-authentication-for-beginnerstypescript

This tutorial shows you how to add authentication to a CrewAI TypeScript app so only approved users can trigger agents and tasks. You need this when your crew is exposed through an API, a dashboard, or any internal tool where access control matters.

What You'll Need

  • Node.js 18+
  • A TypeScript project with crewai installed
  • An API framework like Express
  • dotenv for environment variables
  • An auth provider or token source, such as:
    • static bearer tokens for internal tools
    • JWTs from your identity provider
    • API keys from a gateway
  • A valid model API key, such as:
    • OPENAI_API_KEY
    • or another provider supported by your CrewAI setup

Install the packages:

npm install crewai express dotenv jsonwebtoken
npm install -D typescript tsx @types/express @types/jsonwebtoken @types/node

Step-by-Step

  1. Start with a normal CrewAI setup, then keep authentication outside the agent layer. The clean pattern is: authenticate the request first, then call the crew only if the user is allowed.
import { Agent, Task, Crew } from "crewai";

const supportAgent = new Agent({
  name: "Support Analyst",
  role: "Customer support triage",
  goal: "Classify incoming requests and draft responses",
  backstory: "You work in a regulated insurance support team.",
});

const task = new Task({
  description: "Review the customer request and produce a response draft.",
  agent: supportAgent,
});

export const crew = new Crew({
  agents: [supportAgent],
  tasks: [task],
});
  1. Add environment-based configuration for your auth secret and model key. This keeps credentials out of code and makes local testing simple.
import "dotenv/config";

export const config = {
  port: Number(process.env.PORT ?? "3000"),
  authToken: process.env.AUTH_TOKEN ?? "",
  jwtSecret: process.env.JWT_SECRET ?? "",
};

if (!process.env.OPENAI_API_KEY) {
  throw new Error("OPENAI_API_KEY is required");
}

if (!config.authToken && !config.jwtSecret) {
  throw new Error("Set AUTH_TOKEN or JWT_SECRET");
}
  1. Create an authentication middleware. For beginners, a static bearer token is the simplest working option, and you can replace it with JWT verification later without changing the crew logic.
import type { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { config } from "./config";

export type AuthUser = { sub: string; role?: string };

declare global {
  namespace Express {
    interface Request {
      user?: AuthUser;
    }
  }
}

export function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization ?? "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : "";

  if (config.authToken && token === config.authToken) {
    req.user = { sub: "internal-user", role: "admin" };
    return next();
  }

  if (config.jwtSecret && token) {
    try {
      req.user = jwt.verify(token, config.jwtSecret) as AuthUser;
      return next();
    } catch {
      return res.status(401).json({ error: "Invalid token" });
    }
  }

  return res.status(401).json({ error: "Unauthorized" });
}
  1. Wrap your CrewAI execution in an authenticated API route. The important part is that the route checks identity before calling crew.kickoff().
import express from "express";
import { crew } from "./crew";
import { authenticate } from "./auth";
import { config } from "./config";

const app = express();
app.use(express.json());

app.post("/run", authenticate, async (req, res) => {
  const input = String(req.body?.input ?? "");

  if (!input.trim()) {
    return res.status(400).json({ error: "input is required" });
  }

  const result = await crew.kickoff({
    inputs: { input, userId: req.user?.sub ?? "unknown" },
  });

  return res.json({
    user: req.user,
    result,
  });
});

app.listen(config.port, () => {
  console.log(`API listening on http://localhost:${config.port}`);
});
  1. Send the authenticated user context into your task prompt. This gives the agent enough context to tailor output without trusting raw client input for identity.
import { Agent, Task, Crew } from "crewai";

const analyst = new Agent({
  name: "Claims Analyst",
  role: "Claims review specialist",
});

const reviewTask = new Task({
  description:
    "User {{userId}} submitted this request: {{input}}. Draft a concise response.",
  agent: analyst,
});

export const authCrew = new Crew({
  agents: [analyst],
  tasks: [reviewTask],
});

Testing It

Run the app with your environment variables set, then call the endpoint without an Authorization header. You should get a 401 Unauthorized response immediately, before any CrewAI work starts.

Next, send a valid bearer token or JWT and confirm that the request returns a crew result along with the decoded user data. If you passed userId into kickoff, verify that it appears in your task input or logs.

A quick curl test looks like this:

curl -X POST http://localhost:3000/run \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-token-here" \
  -d '{"input":"Summarize this customer complaint"}'

If you are using JWTs, test both an expired token and a malformed one. Both should fail with 401, which tells you authentication is happening at the edge instead of inside your agent flow.

Next Steps

  • Replace static bearer tokens with JWT validation against your real identity provider.
  • Add role-based authorization so only certain users can run specific crews or tasks.
  • Store audit logs for every authenticated crew execution, including user ID, timestamp, and task name.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides