CrewAI Tutorial (TypeScript): adding authentication for beginners
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
crewaiinstalled - •An API framework like Express
- •
dotenvfor 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
- •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],
});
- •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");
}
- •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" });
}
- •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}`);
});
- •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
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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