Claude Code से सुरक्षित Authentication: Next.js Session, JWT Boundary और OAuth
Claude Code से सुरक्षित Next.js auth बनाएं: session, JWT, OAuth, CSRF, RBAC, audit log और tests।
Authentication केवल login form नहीं है। Production से पहले password storage, session cookie, JWT, OAuth callback, CSRF, password reset, RBAC, secrets, audit logs और tests साथ में design करने पड़ते हैं। अगर Claude Code को सिर्फ “auth जोड़ दो” कहा गया, तो UI चल सकता है लेकिन security boundary कमजोर रह सकती है।
सबसे common unsafe shortcut है long-lived JWT को browser localStorage में रखना। XSS, यानी page में malicious JavaScript चलने की स्थिति में token सीधे चोरी हो सकता है। Normal web app के लिए server-side session बेहतर default है: server login state रखता है, browser को केवल random session ID मिलता है, और वह HttpOnly Cookie से भेजा जाता है।
यह guide Next.js App Router के लिए copy-pasteable demo देती है। इसमें Zod validation, bcrypt password hashing, signed opaque session cookie, middleware guard, CSRF और Origin check, RBAC, audit log और Vitest tests हैं। JWT को short-lived API/mobile boundary माना गया है। OAuth को identity provider boundary माना गया है, local authorization का replacement नहीं।
Official references हमेशा मूल docs से check करें: Next.js Authentication guide, Next.js cookies API, OWASP Authentication Cheat Sheet, OWASP Password Storage Cheat Sheet, OWASP Forgot Password Cheat Sheet, MDN Secure cookie configuration, Auth.js और Claude Code docs। Related articles: Cookie management, RBAC, Zod validation।
Session, JWT और OAuth अलग रखें
Session पूछता है: “यह browser अभी logged in है या नहीं?” JWT पूछता है: “यह client थोड़े समय के लिए signed claims prove कर सकता है या नहीं?” OAuth/OIDC पूछता है: “क्या trusted provider ने इस user को authenticate किया?” इन तीनों को एक जगह मिलाने से review मुश्किल होता है।
| Mechanism | Best use | Strength | Risk |
|---|---|---|---|
| Server-side session | SaaS dashboard, member area, admin | revoke और audit आसान | Redis, Postgres, DynamoDB जैसा store चाहिए |
| JWT | mobile API, short service call, external API | DB lookup के बिना signature verify | revoke मुश्किल; browser में long storage avoid करें |
| OAuth / OIDC | Google, GitHub, company SSO | user password खुद store नहीं करना पड़ता | provider login local permission को replace नहीं करता |
तीन use cases practical हैं। SaaS dashboard में HttpOnly session cookie रखें और billing/email change पर reauthentication मांगें। Paid content site में free reader, buyer, editor और admin को RBAC से अलग करें। Internal B2B tool में Google Workspace login हो सकता है, लेकिन tenant boundary, role, expiry और revocation app को खुद check करना होगा।
Claude Code को सही prompt दें
Code मांगने से पहले security contract दें।
Next.js App Router app के लिए authentication implement करें।
Requirements:
- Browser login server-side session और HttpOnly Cookie से हो
- JWT सिर्फ short-lived external API token के लिए हो; localStorage न इस्तेमाल करें
- Password bcrypt या Argon2id से hash करें, plaintext न रखें
- Input Zod से validate करें और email exists है या नहीं leak न करें
- Cookie में Secure, HttpOnly, SameSite, Path, Max-Age explicitly set करें
- State-changing API में Origin check और CSRF token verify करें
- OAuth के लिए Auth.js जैसी proven library use करें
- RBAC, password reset notes, audit logs और tests शामिल करें
- अंत में pitfalls और verification commands दें
यह prompt Claude Code को unsafe default लेने से रोकता है। Authentication में पहला generated code कम, boundary और tests ज्यादा important हैं।
Next.js minimal implementation
Dependencies install करें।
npm install zod bcryptjs
npm install -D vitest typescript @types/node
.env.local में 32 characters से लंबा secret रखें। इसे Git में commit न करें।
SESSION_SECRET="replace-with-at-least-32-random-characters"
lib/auth/password.ts। OWASP Argon2id को modern choice मानता है; demo में copy-paste simplicity के लिए bcryptjs है।
import bcrypt from "bcryptjs";
import { z } from "zod";
export const passwordSchema = z.string().min(12).max(128);
export async function hashPassword(password: string) {
const parsed = passwordSchema.parse(password);
return bcrypt.hash(parsed, 12);
}
export async function verifyPassword(password: string, hash: string) {
return bcrypt.compare(password, hash);
}
lib/auth/session.ts। Memory Map demo के लिए है; production में Redis, PostgreSQL या DynamoDB use करें।
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
import { z } from "zod";
const env = z
.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
SESSION_SECRET: z.string().min(32),
})
.parse(process.env);
export type Role = "user" | "admin";
type SessionRecord = { userId: string; role: Role; csrfToken: string; expiresAt: number };
declare global {
var demoSessions: Map<string, SessionRecord> | undefined;
}
const sessions = globalThis.demoSessions ?? new Map<string, SessionRecord>();
globalThis.demoSessions = sessions;
export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 8;
export const SESSION_COOKIE_NAME =
env.NODE_ENV === "production" ? "__Host-session" : "dev-session";
export const sessionCookieOptions = {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "lax" as const,
path: "/",
maxAge: SESSION_MAX_AGE_SECONDS,
};
function signSessionId(sessionId: string) {
return createHmac("sha256", env.SESSION_SECRET).update(sessionId).digest("base64url");
}
function safeEqual(left: string, right: string) {
const a = Buffer.from(left);
const b = Buffer.from(right);
return a.length === b.length && timingSafeEqual(a, b);
}
export function createSession(userId: string, role: Role = "user") {
const sessionId = randomBytes(32).toString("base64url");
const token = `${sessionId}.${signSessionId(sessionId)}`;
const csrfToken = randomBytes(32).toString("base64url");
sessions.set(sessionId, {
userId,
role,
csrfToken,
expiresAt: Date.now() + SESSION_MAX_AGE_SECONDS * 1000,
});
return { token, csrfToken };
}
export function getSession(token?: string) {
if (!token) return null;
const [sessionId, signature] = token.split(".");
if (!sessionId || !signature || !safeEqual(signature, signSessionId(sessionId))) return null;
const session = sessions.get(sessionId);
if (!session || session.expiresAt < Date.now()) {
sessions.delete(sessionId);
return null;
}
return { id: sessionId, ...session };
}
export function destroySession(token?: string) {
const sessionId = token?.split(".")[0];
if (sessionId) sessions.delete(sessionId);
}
export function assertSameOrigin(request: Request) {
const origin = request.headers.get("origin");
if (origin && origin !== new URL(request.url).origin) throw new Error("Bad origin");
}
export function assertCsrf(request: Request, session: { csrfToken: string }) {
const submitted = request.headers.get("x-csrf-token");
if (!submitted || submitted !== session.csrfToken) throw new Error("Bad CSRF token");
}
Login route app/api/login/route.ts। Real app में hash और role database से आएंगे।
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { hashPassword, verifyPassword } from "@/lib/auth/password";
import { SESSION_COOKIE_NAME, createSession, sessionCookieOptions } from "@/lib/auth/session";
export const runtime = "nodejs";
export const loginInputSchema = z.object({
email: z.string().trim().toLowerCase().email(),
password: z.string().min(12).max(128),
});
async function findUserByEmail(email: string) {
if (email !== "masa@example.com") return null;
return {
id: "user_123",
role: "admin" as const,
passwordHash: await hashPassword("correct-horse-battery-staple"),
};
}
export async function POST(request: NextRequest) {
const parsed = loginInputSchema.safeParse(await request.json());
if (!parsed.success) return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
const user = await findUserByEmail(parsed.data.email);
const passwordOk = user ? await verifyPassword(parsed.data.password, user.passwordHash) : false;
if (!user || !passwordOk) {
return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
}
const session = createSession(user.id, user.role);
const response = NextResponse.json({ ok: true, csrfToken: session.csrfToken });
response.cookies.set({ name: SESSION_COOKIE_NAME, value: session.token, ...sessionCookieOptions });
response.cookies.set({
name: "csrf-token",
value: session.csrfToken,
secure: sessionCookieOptions.secure,
sameSite: "lax",
path: "/",
maxAge: sessionCookieOptions.maxAge,
});
return response;
}
middleware.ts सिर्फ routing guard है। Real authorization server route में दोबारा check करें।
import { NextRequest, NextResponse } from "next/server";
const SESSION_COOKIE_NAME =
process.env.NODE_ENV === "production" ? "__Host-session" : "dev-session";
export function middleware(request: NextRequest) {
const hasSession = request.cookies.has(SESSION_COOKIE_NAME);
const pathname = request.nextUrl.pathname;
if (!hasSession && (pathname.startsWith("/dashboard") || pathname.startsWith("/admin"))) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*", "/admin/:path*"] };
test/auth.test.ts।
import { beforeAll, describe, expect, it } from "vitest";
beforeAll(() => {
process.env.NODE_ENV = "test";
process.env.SESSION_SECRET = "test-secret-value-with-more-than-32-characters";
});
describe("auth primitives", () => {
it("hashes and verifies passwords", async () => {
const { hashPassword, verifyPassword } = await import("../lib/auth/password");
const hash = await hashPassword("correct-horse-battery-staple");
await expect(verifyPassword("correct-horse-battery-staple", hash)).resolves.toBe(true);
await expect(verifyPassword("wrong-password", hash)).resolves.toBe(false);
});
it("creates and destroys a session", async () => {
const { createSession, destroySession, getSession } = await import("../lib/auth/session");
const session = createSession("user_123", "admin");
expect(getSession(session.token)?.role).toBe("admin");
destroySession(session.token);
expect(getSession(session.token)).toBeNull();
});
it("validates login input", async () => {
const { loginInputSchema } = await import("../app/api/login/route");
expect(loginInputSchema.safeParse({ email: "bad", password: "short" }).success).toBe(false);
});
});
Password reset, OAuth और JWT
Password reset में email exists है या नहीं, यह response से leak नहीं होना चाहिए। Reset token high-entropy हो, database में सिर्फ hash रहे, expiry short हो और token single-use हो। Password change के बाद existing sessions invalidate करने का option दें।
OAuth खुद से implement न करें। Auth.js जैसी library provider callback संभालती है। उसके बाद provider identity को local user से link करें और अपनी session cookie issue करें। Provider access token को app session cookie न बनाएं।
JWT API के लिए useful है, लेकिन universal solution नहीं। Use करते समय aud, iss, exp, key rotation और leak response लिखें।
Pitfalls, audit log और monetization
Deploy से पहले ये mistakes रोकें: long JWT in localStorage, Cookie without Secure or HttpOnly, plain SHA-256 password hash, reset token plaintext DB में, login error से user enumeration, RBAC सिर्फ middleware में, logs में password/token print करना।
Audit log में actor, action, result और time रखें। Password, session ID, reset token, OAuth access token न रखें। SaaS में role check से पहले tenantId check करें।
Authentication revenue path भी बचाता है: paid content, templates, Gumroad links, B2B forms और member dashboard। पहले free checklist से Claude Code review flow fix करें। reusable resources चाहिए तो products/templates देखें, और team-level auth, RBAC, audit logs और CI tests के लिए Claude Code training/consultation उपयोगी है।
Hands-on result
Masa ने इस flow को test किया तो सबसे बड़ा gain login route नहीं था। Session cookie, CSRF, RBAC, audit log और tests को एक task unit बनाने से review clear हुआ। पहले middleware security boundary जैसा दिखता था, पर वह सिर्फ cookie presence check करता था। Real checks server route में लाने से admin page mistakes कम हुईं।
Summary
Claude Code से auth बनाते समय पहले boundary तय करें। Browser के लिए server-side session और secure Cookie, API के लिए short-lived JWT, external login के लिए OAuth/OIDC library, state change के लिए CSRF और Origin, authorization के लिए RBAC, sensitive actions के लिए audit logs और tests रखें।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Obsidian to CLAUDE.md workflow: context बार-बार न समझाएं
Obsidian notes को CLAUDE.md operating notes में बदलकर Claude Code sessions को resume करना आसान बनाएं.
Claude Code Revenue CTA Routing: article से PDF, Gumroad और consultation तक
Reader intent के आधार पर free PDF, Gumroad products और consultation तक CTA route करने वाला workflow.
Claude Code टीम हैंडऑफ नियम: review proof, permissions, rollback और revenue path
Claude Code टीम काम के लिए evidence, permission rules, rollback, free PDF, Gumroad और consultation path वाला handoff.