Claude Code से RBAC लागू करना: authentication, authorization, tenant boundary और audit log
Claude Code के साथ सुरक्षित RBAC बनाएं: permission model, Express middleware, tenant boundary, tests, audit logs और ABAC की सीमा।
RBAC सिर्फ इसलिए सुरक्षित नहीं हो जाता कि आपकी app में admin, editor और viewer जैसे roles हैं। असली गलती अक्सर छोटी जगहों पर होती है: logged-in user को अपने आप authorized मान लेना, किसी DB query में tenant_id filter भूल जाना, UI में button छिपा देना लेकिन API खुली छोड़ देना, या editor को guessed ID से दूसरे user का object update करने देना।
यह guide RBAC को Claude Code के लिए reviewable काम में बदलती है। हम authentication और authorization का फर्क, role / permission / resource / action, deny by default, tenant boundary, object-level authorization, Express middleware, database schema, tests, audit logs और ABAC पर जाने की सीमा समझेंगे। Login और token layer के लिए Claude Code JWT authentication guide देखें, और पूरे security setup के लिए Claude Code security best practices भी पढ़ें।
बाहरी reference के रूप में OWASP Authorization Cheat Sheet, OWASP Access Control, Casbin RBAC और Auth0 Core RBAC उपयोगी हैं। OWASP least privilege, deny by default, हर protected request पर permission check, logging और authorization tests पर जोर देता है।
Authentication aur authorization alag hain
Authentication का सवाल है: यह user कौन है? Password, SSO, JWT, session cookie और MFA इसी layer में आते हैं। Authorization का सवाल है: क्या यह actor इस resource पर यह action कर सकता है? User का login होना invoice delete करने, admin invite करने या दूसरे customer का data पढ़ने की अनुमति नहीं है।
RBAC design में चार शब्द साफ रखें:
| Element | Example | Review question |
|---|---|---|
| role | viewer, editor, billing_admin, owner | क्या यह job responsibility पर आधारित है |
| permission | article:update, invoice:read | क्या यह code में एक जगह defined है |
| resource | article, invoice, user | कौन सा object protected है |
| action | read, create, update, delete | क्या यह HTTP method से ज्यादा precise है |
सबसे आम गलती है बड़ा admin role बनाना और फिर exceptions जोड़ते जाना। Review में समझना मुश्किल हो जाता है कि exception product requirement है या temporary bypass। बेहतर तरीका है पहले permissions define करना और roles को permission bundles की तरह treat करना।
flowchart TD
A["Authenticated actor"] --> B["Tenant boundary"]
B --> C["Role to permission map"]
C --> D["Resource and action check"]
D --> E["Object-level rule"]
E --> F["Allow"]
B --> X["Deny and audit"]
C --> X
D --> X
E --> X
deny by default se shuruaat karein
Deny by default का मतलब है कि जो operation साफ तौर पर allow नहीं है, वह reject होगा। Claude Code को authorization code पर काम देते समय rejection rules पहले लिखें: unknown permission, empty role list, tenant mismatch, missing resource और object owner mismatch सब fail closed होने चाहिए।
नीचे Express example copy-paste करके चलाया जा सकता है। Demo identity headers से पढ़ता है ताकि tests clear रहें। Production में इसे verified JWT या session result से replace करें।
{
"name": "rbac-express-demo",
"type": "module",
"scripts": {
"dev": "tsx src/server.ts",
"test": "vitest run"
},
"dependencies": {
"express": "^5.1.0"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^22.15.0",
"@types/supertest": "^6.0.3",
"supertest": "^7.1.1",
"tsx": "^4.19.4",
"typescript": "^5.8.3",
"vitest": "^3.1.4"
}
}
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "test/**/*.ts"]
}
TypeScript mein RBAC core
Role checks को हर controller में मत फैलाइए। Decision को authorize function में रखें और routes सिर्फ permission name pass करें। इससे Claude Code को नया resource जोड़ते समय छोटा और reviewable diff बनाना आसान होता है।
// src/rbac.ts
export const permissions = [
"project:read",
"article:read",
"article:create",
"article:update",
"article:delete",
"invoice:read",
"invoice:refund",
"user:manage"
] as const;
export type Permission = (typeof permissions)[number];
export type Role = "viewer" | "editor" | "billing_admin" | "owner";
export type Actor = {
id: string;
tenantId: string;
roles: Role[];
};
export type ResourceRecord = {
id: string;
tenantId: string;
ownerId?: string;
};
export type AuthorizationDecision = {
allow: boolean;
reason: string;
};
export const rolePermissions = {
viewer: ["project:read", "article:read", "invoice:read"],
editor: ["project:read", "article:read", "article:create", "article:update"],
billing_admin: ["project:read", "invoice:read", "invoice:refund"],
owner: [...permissions]
} as const satisfies Record<Role, readonly Permission[]>;
const knownPermissions = new Set<Permission>(permissions);
export function authorize(
actor: Actor,
permission: Permission,
record?: ResourceRecord
): AuthorizationDecision {
if (!knownPermissions.has(permission)) {
return { allow: false, reason: "unknown_permission" };
}
if (actor.roles.length === 0) {
return { allow: false, reason: "no_role" };
}
if (record && record.tenantId !== actor.tenantId) {
return { allow: false, reason: "tenant_mismatch" };
}
const roleAllows = actor.roles.some((role) =>
rolePermissions[role].includes(permission)
);
if (!roleAllows) {
return { allow: false, reason: "role_missing_permission" };
}
if (
permission === "article:update" &&
!actor.roles.includes("owner") &&
record?.ownerId !== actor.id
) {
return { allow: false, reason: "not_resource_owner" };
}
return { allow: true, reason: "allowed" };
}
export function auditAuthorization(input: {
actor?: Actor;
permission: Permission;
resourceId?: string;
decision: AuthorizationDecision;
}) {
console.info(
JSON.stringify({
type: "authorization",
actorId: input.actor?.id ?? "anonymous",
tenantId: input.actor?.tenantId ?? "unknown",
permission: input.permission,
resourceId: input.resourceId ?? null,
allow: input.decision.allow,
reason: input.decision.reason,
at: new Date().toISOString()
})
);
}
Route यह नहीं पूछती कि user editor है या नहीं। Route article:update मांगती है। Role सिर्फ permission resolve करता है; tenant boundary और owner rule अलग से चलते हैं।
Express middleware har request par
Authorization handler के data बदलने से पहले चलना चाहिए। Middleware से protected routes साफ दिखती हैं और भूलने की संभावना कम होती है।
// src/server.ts
import express, { type NextFunction, type Request, type Response } from "express";
import {
type Actor,
type Permission,
type ResourceRecord,
type Role,
auditAuthorization,
authorize,
rolePermissions
} from "./rbac.js";
declare global {
namespace Express {
interface Request {
actor?: Actor;
}
}
}
type Article = ResourceRecord & {
title: string;
body: string;
};
const articles: Article[] = [
{ id: "a1", tenantId: "tenant-a", ownerId: "user-1", title: "Roadmap", body: "Draft" },
{ id: "a2", tenantId: "tenant-a", ownerId: "user-2", title: "Release", body: "Ready" },
{ id: "b1", tenantId: "tenant-b", ownerId: "user-9", title: "Private", body: "Secret" }
];
function parseRoles(value: string | undefined): Role[] {
return (value ?? "")
.split(",")
.map((role) => role.trim())
.filter((role): role is Role => role in rolePermissions);
}
function authenticateForDemo(req: Request, _res: Response, next: NextFunction) {
const userId = req.header("x-user-id");
const tenantId = req.header("x-tenant-id");
if (userId && tenantId) {
req.actor = {
id: userId,
tenantId,
roles: parseRoles(req.header("x-roles"))
};
}
next();
}
function findArticle(req: Request): Article | undefined {
return articles.find((article) => article.id === req.params.articleId);
}
function requirePermission(
permission: Permission,
loadResource?: (req: Request) => ResourceRecord | undefined
) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.actor) {
return res.status(401).json({ error: "unauthenticated" });
}
const record = loadResource?.(req);
if (loadResource && !record) {
return res.status(404).json({ error: "not_found" });
}
const decision = authorize(req.actor, permission, record);
auditAuthorization({
actor: req.actor,
permission,
resourceId: record?.id,
decision
});
if (!decision.allow) {
return res.status(403).json({ error: "forbidden", reason: decision.reason });
}
return next();
};
}
export const app = express();
app.use(express.json());
app.use(authenticateForDemo);
app.get("/health", (_req, res) => {
res.json({ ok: true });
});
app.get(
"/articles/:articleId",
requirePermission("article:read", findArticle),
(req, res) => {
res.json(findArticle(req));
}
);
app.patch(
"/articles/:articleId",
requirePermission("article:update", findArticle),
(req, res) => {
const article = findArticle(req);
if (!article) return res.status(404).json({ error: "not_found" });
article.title = String(req.body.title ?? article.title);
article.body = String(req.body.body ?? article.body);
return res.json(article);
}
);
app.delete(
"/articles/:articleId",
requirePermission("article:delete", findArticle),
(req, res) => {
const index = articles.findIndex((article) => article.id === req.params.articleId);
if (index >= 0) articles.splice(index, 1);
return res.status(204).send();
}
);
app.get("/admin/users", requirePermission("user:manage"), (_req, res) => {
res.json([{ id: "user-1" }, { id: "user-2" }]);
});
if (process.env.NODE_ENV !== "test") {
app.listen(3000, () => {
console.log("RBAC demo listening on http://localhost:3000");
});
}
Production में authenticateForDemo को verified identity source से बदलें। IdP अगर roles या permissions token में भेजता है, तब भी API को server side पर tenant और object rules दोबारा check करने चाहिए।
Database schema mein tenant boundary
Authorization bug middleware से पहले भी हो सकता है: query गलत row fetch करती है और handler उसे safe मान लेता है। इसलिए schema में tenant_id, composite unique constraints और audit logs साफ होने चाहिए।
CREATE TABLE tenants (
id TEXT PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE users (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL REFERENCES tenants(id),
email TEXT NOT NULL,
UNIQUE (tenant_id, email)
);
CREATE TABLE roles (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL REFERENCES tenants(id),
name TEXT NOT NULL,
UNIQUE (tenant_id, name)
);
CREATE TABLE permissions (
id TEXT PRIMARY KEY,
resource TEXT NOT NULL,
action TEXT NOT NULL,
UNIQUE (resource, action)
);
CREATE TABLE user_roles (
user_id TEXT NOT NULL REFERENCES users(id),
role_id TEXT NOT NULL REFERENCES roles(id),
tenant_id TEXT NOT NULL REFERENCES tenants(id),
PRIMARY KEY (user_id, role_id)
);
CREATE TABLE role_permissions (
role_id TEXT NOT NULL REFERENCES roles(id),
permission_id TEXT NOT NULL REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE articles (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL REFERENCES tenants(id),
owner_id TEXT NOT NULL REFERENCES users(id),
title TEXT NOT NULL,
body TEXT NOT NULL
);
CREATE TABLE authorization_audit_logs (
id TEXT PRIMARY KEY,
tenant_id TEXT,
actor_id TEXT,
permission TEXT NOT NULL,
resource_id TEXT,
allowed BOOLEAN NOT NULL,
reason TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_articles_tenant_owner ON articles(tenant_id, owner_id);
CREATE INDEX idx_audit_tenant_created ON authorization_audit_logs(tenant_id, created_at);
B2B SaaS में custom tenant roles आम होते हैं, इसलिए tenant_id + name unique रखना practical है। Regulated systems में global role definitions और local assignments audit के लिए बेहतर हो सकते हैं।
Tests mein denial cases badhayein
RBAC tests को allowed cases दिखाने चाहिए, पर denied cases ज्यादा जरूरी हैं: same role but different tenant, same tenant but different owner, logged-in user without permission, missing resource, और unauthenticated request।
// test/rbac.test.ts
import request from "supertest";
import { describe, expect, it } from "vitest";
import { app } from "../src/server.js";
const editorUser1 = {
"x-user-id": "user-1",
"x-tenant-id": "tenant-a",
"x-roles": "editor"
};
const editorTenantB = {
"x-user-id": "user-9",
"x-tenant-id": "tenant-b",
"x-roles": "editor"
};
const owner = {
"x-user-id": "owner-1",
"x-tenant-id": "tenant-a",
"x-roles": "owner"
};
describe("RBAC middleware", () => {
it("rejects unauthenticated requests", async () => {
const res = await request(app).get("/articles/a1");
expect(res.status).toBe(401);
});
it("allows an editor to update an owned article in the same tenant", async () => {
const res = await request(app)
.patch("/articles/a1")
.set(editorUser1)
.send({ title: "Updated roadmap" });
expect(res.status).toBe(200);
expect(res.body.title).toBe("Updated roadmap");
});
it("blocks cross-tenant access even when the role matches", async () => {
const res = await request(app).get("/articles/a1").set(editorTenantB);
expect(res.status).toBe(403);
expect(res.body.reason).toBe("tenant_mismatch");
});
it("blocks editors from managing users", async () => {
const res = await request(app).get("/admin/users").set(editorUser1);
expect(res.status).toBe(403);
expect(res.body.reason).toBe("role_missing_permission");
});
it("allows owners to delete tenant resources", async () => {
const res = await request(app).delete("/articles/a2").set(owner);
expect(res.status).toBe(204);
});
});
npm install
npm test
npm run dev
Claude Code से tests जोड़वाते समय tenant_mismatch, not_resource_owner, role_missing_permission और unauthenticated साफ लिखें। Client response का reason audit log के reason से match होना चाहिए।
Teen se zyada practical use cases
पहला use case B2B project management है। Owner members invite करता है और billing बदलता है, editor सिर्फ content बदलता है। यहां tenant boundary role name से ज्यादा महत्वपूर्ण है। Guessed projectId query और middleware दोनों में reject होना चाहिए।
दूसरा use case internal CMS है। Editor अपने drafts update कर सकता है, पर published article delete करना या दूसरे author का article edit करना stronger role मांगता है। यह RBAC के ऊपर object-level authorization है, क्योंकि article:update में ownerId और status भी मायने रखते हैं।
तीसरा use case billing और refund है। billing_admin invoices पढ़ सकता है, पर refund amount, approval status या second reviewer पर depend कर सकता है। RBAC gate खोलता है; business attributes final decision देते हैं।
चौथा use case support impersonation है। Support customer workspace देख सकता है, पर email, payment method या owner role नहीं बदल सकता। हर impersonated action को customer के अपने action से अलग audit log में रखना चाहिए।
Failure examples aur fixes
| Failure | Risk | Fix |
|---|---|---|
| Logged-in user को सभी records लौटाना | Horizontal privilege escalation | हर query में tenant_id और resource ID filter करें |
| सिर्फ UI button hide करना | Direct API call फिर भी चलेगी | Server middleware में authorization enforce करें |
एक बड़ा admin role | Permission review बेकार हो जाती है | Permissions split करके roles redesign करें |
| सिर्फ allowed actions log करना | Attack probing दिखता नहीं | allow और deny दोनों reason के साथ log करें |
| सिर्फ success tests | Regression छिपी रह जाती है | denial matrix automate करें |
UI security boundary नहीं है। Button hide करना UX सुधार है, endpoint protection नहीं। असली protection server-side authorization में होनी चाहिए।
Claude Code ko kya delegate karein
Claude Code clear policy को code, tests और reviewable diff में बदलने के लिए उपयोगी है। उसे security policy खुद decide करने न दें। Files, denial rules और review criteria पहले दें।
Help implement RBAC.
Only change src/rbac.ts, src/server.ts, and test/rbac.test.ts.
Requirements:
- An authenticated actor has userId, tenantId, and roles
- Permissions use the resource:action format
- Unknown permission, empty role list, and tenant mismatch deny by default
- article:update is allowed only for owner role or the article owner
- 403 responses include a reason, and audit logs use the same reason
- Add more denial tests than success tests
Do not:
- Add an admin bypass
- Treat UI visibility as authorization
- Modify the existing authentication flow
Review में देखें कि authorize ही decision point है, protected routes middleware से गुजरती हैं, object queries में tenant condition है, audit logs token या secret leak नहीं करते, और tests denial cases cover करते हैं। Claude Code बड़ा diff बनाए तो authorization changes को unrelated refactor से अलग करें।
RBAC se ABAC kab
RBAC job responsibilities के लिए अच्छा है। लेकिन जब rule attributes पर depend करने लगे तो roles बहुत बढ़ते हैं: high-value refund में dual approval, EU personal data सिर्फ EU region से view करना, audit export सिर्फ Enterprise plan में, या office hours के बाहर permission block करना।
ABAC का मतलब Attribute-Based Access Control है, यानी actor, resource, environment और request attributes देखकर decision लेना। Practical तरीका है RBAC को coarse gate रखना और जहां business condition चाहिए वहां ABAC-style checks जोड़ना। Policy engine इस्तेमाल करें तो भी permission names, tenant boundary, audit logs और tests पहले clear रखें।
Summary
अच्छा RBAC clear denial behavior से पहचाना जाता है। Authentication और authorization अलग रखें, permission को resource:action में define करें, deny by default लागू करें, tenant boundary और object rules middleware में check करें, और tests plus audit logs से साबित करें। इससे Claude Code को सुरक्षित scope मिलता है: policy implement करना, denial tests जोड़ना और diff reviewable रखना।
पूरा API flow बनाते समय Claude Code API development guide, JWT authentication guide और security best practices को साथ पढ़ें। Consulting, training या implementation support चाहिए तो current role table, protected API list और सबसे risky operations तैयार रखें; इससे पहला review ज्यादा सटीक होगा।
इस article की approach try करते समय चार बातें check करें: denial tests pass हों, दूसरे tenant के IDs 403 दें, audit logs में allow और deny दोनों दिखें, और Claude Code ने authorize bypass करने वाला shortcut न जोड़ा हो। पहले एक dangerous operation चुनें, role, permission, resource, action, tenantId और ownerId लिखें, फिर उसे code में बदलें।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Permission Receipt Pattern: scope, proof और rollback लिखना
Claude Code के लिए permission receipt: allowed actions, approval boundary, verification commands, rollback note और revenue CTA checks।
Claude Code और Codex के लिए सुरक्षित Agent Harness: permissions, verification और rollback
Claude Code और Codex agents के लिए सुरक्षित harness: permissions, plan, verification और rollback.
Claude Code Subagents गाइड: article और code work को सुरक्षित तरीके से delegate करें
Claude Code subagents से article और code work बांटें: delegation rules, prompts, pitfalls, checklist और examples.