Claude Code से 2FA लागू करें: TOTP, बैकअप कोड, रिकवरी और Step-Up Auth
Next.js में Claude Code के साथ सुरक्षित 2FA/MFA लागू करें: TOTP, बैकअप कोड, रिकवरी, rate limit, passkeys और audit logs.
2FA सिर्फ QR code दिखाने वाली स्क्रीन नहीं है। production में भरोसेमंद 2FA/MFA के लिए TOTP, backup codes, sensitive actions के लिए step-up auth, account recovery, rate limits, remembered devices, audit logs और WebAuthn/passkeys की तरफ बढ़ने का रास्ता चाहिए।
यह गाइड Next.js App Router, TypeScript और Prisma के संदर्भ में है। उद्देश्य यह है कि Claude Code से तेज़ी से कोड लिखवाया जाए, लेकिन सुरक्षा की सीमा इंसान के नियंत्रण में रहे। login layer के लिए Claude Code JWT authentication guide, password recovery के लिए Claude Code password reset guide, permissions के लिए Claude Code RBAC guide, और agent permissions के लिए Claude Code security best practices भी देखें।
विश्वसनीय बाहरी आधार के लिए OWASP MFA Cheat Sheet, OWASP WSTG MFA testing, MDN Web Authentication API और W3C WebAuthn Level 3 पढ़ें। ये स्रोत बताते हैं कि MFA में OTP storage, failed attempts, recovery और factor changes भी उतने ही महत्वपूर्ण हैं।
सुरक्षा सीमा पहले तय करें
TOTP का मतलब time-based one-time password है। server और authenticator app एक secret साझा करते हैं और आम तौर पर हर 30 seconds में 6 digit code बनाते हैं। MFA session की assurance बढ़ाता है, लेकिन authorization की जगह नहीं लेता। admin invite, billing change, API key creation, password change और personal data export जैसे actions पर fresh MFA चाहिए।
flowchart TD
A["Password or SSO login"] --> B{"2FA enabled?"}
B -->|No| C["Lower-assurance session"]
B -->|Yes| D["TOTP or backup code challenge"]
D --> E{"Valid and rate limit OK?"}
E -->|No| F["Deny and audit"]
E -->|Yes| G["Session with mfaAt"]
G --> H{"Sensitive action?"}
H -->|Yes| I{"mfaAt fresh?"}
I -->|No| D
I -->|Yes| J["Allow"]
H -->|No| J
Claude Code को यही boundary पहले दें। “2FA बना दो” जैसा बड़ा prompt न दें। काम को database schema, crypto helpers, setup API, enable API, login challenge, recovery flow, remembered device, audit logs और tests में बांटें। production secrets, KMS, support policy और admin permissions को prompt में न डालें।
Implement 2FA for a Next.js App Router app with TypeScript and Prisma.
Scope: TOTP, backup codes, remembered devices, step-up auth, recovery events, audit logs.
Rules:
- Encrypt TOTP secrets with AES-256-GCM before saving.
- Hash backup codes and remembered device tokens with HMAC-SHA256.
- Apply rate limits to setup, enable, login challenge, and recovery.
- Do not make SMS the primary factor.
- Keep a migration path to WebAuthn/passkeys.
First output the Prisma schema and review checklist only.
review में इन बातों को पकड़ें: TOTP secret plaintext में है या नहीं, backup codes plaintext या logs में हैं या नहीं, backup code दोबारा इस्तेमाल हो सकता है या नहीं, 6 digit challenge पर rate limit है या नहीं, remembered device token raw save तो नहीं हो रहा, support agent अकेले MFA disable तो नहीं कर सकता, और enable, disable, failure, recovery, factor change पर audit log है या नहीं।
Data model
TOTP secret को encrypt करके रखना पड़ता है क्योंकि server को बाद में उसे verify करने के लिए पढ़ना होगा। backup codes और remembered device tokens के लिए comparison काफी है, इसलिए HMAC hash save करें।
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
twoFactorEnabled Boolean @default(false)
twoFactorEnabledAt DateTime?
twoFactorSecretCiphertext String?
twoFactorSecretIv String?
twoFactorSecretTag String?
backupCodes BackupCode[]
rememberedDevices RememberedDevice[]
auditLogs AuditLog[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model BackupCode {
id String @id @default(cuid())
userId String
codeHash String
usedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, usedAt])
@@unique([userId, codeHash])
}
model RememberedDevice {
id String @id @default(cuid())
userId String
deviceHash String
userAgent String?
expiresAt DateTime
lastUsedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, expiresAt])
@@unique([userId, deviceHash])
}
model AuditLog {
id String @id @default(cuid())
userId String?
action String
metadata Json?
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([userId, createdAt])
@@index([action, createdAt])
}
# .env.example
DATABASE_URL="postgresql://user:password@localhost:5432/app"
TWO_FACTOR_ENCRYPTION_KEY="base64-encoded-32-byte-key"
TWO_FACTOR_HASH_SECRET="long-random-hmac-secret"
Encryption, hash और rate limit
// src/lib/two-factor.ts
import { createCipheriv, createDecipheriv, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
import { authenticator } from "otplib";
authenticator.options = { step: 30, window: 1 };
const buckets = new Map<string, { count: number; resetAt: number }>();
function encryptionKey() {
const key = Buffer.from(process.env.TWO_FACTOR_ENCRYPTION_KEY ?? "", "base64");
if (key.length !== 32) throw new Error("TWO_FACTOR_ENCRYPTION_KEY must be 32 bytes in base64.");
return key;
}
function hmacSecret() {
const secret = process.env.TWO_FACTOR_HASH_SECRET;
if (!secret || secret.length < 32) throw new Error("TWO_FACTOR_HASH_SECRET is too short.");
return secret;
}
export function encryptSecret(secret: string) {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", encryptionKey(), iv);
const ciphertext = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
return { ciphertext: ciphertext.toString("base64"), iv: iv.toString("base64"), tag: cipher.getAuthTag().toString("base64") };
}
export function decryptSecret(input: { ciphertext: string; iv: string; tag: string }) {
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(), Buffer.from(input.iv, "base64"));
decipher.setAuthTag(Buffer.from(input.tag, "base64"));
return Buffer.concat([decipher.update(Buffer.from(input.ciphertext, "base64")), decipher.final()]).toString("utf8");
}
export function verifyTotp(token: string, secret: string) {
return /^\d{6}$/.test(token) && authenticator.verify({ token, secret });
}
export function generateBackupCodes(count = 10) {
return Array.from({ length: count }, () => {
const raw = randomBytes(5).toString("hex").toUpperCase();
return `${raw.slice(0, 5)}-${raw.slice(5)}`;
});
}
export function hashBackupCode(userId: string, code: string) {
const normalized = code.replace(/[^a-zA-Z0-9]/g, "").toUpperCase();
return createHmac("sha256", hmacSecret()).update(`backup:${userId}:${normalized}`).digest("hex");
}
export function hashRememberedDevice(userId: string, token: string) {
return createHmac("sha256", hmacSecret()).update(`remember:${userId}:${token}`).digest("hex");
}
export function safeEqualHex(a: string, b: string) {
const left = Buffer.from(a, "hex");
const right = Buffer.from(b, "hex");
return left.length === right.length && timingSafeEqual(left, right);
}
export function consumeRateLimit(key: string, limit: number, windowMs: number) {
const now = Date.now();
const bucket = buckets.get(key);
if (!bucket || bucket.resetAt <= now) {
buckets.set(key, { count: 1, resetAt: now + windowMs });
return true;
}
bucket.count += 1;
return bucket.count <= limit;
}
यह in-memory limiter local development के लिए है। production में Claude Code से इसे Redis या Upstash पर बदलवाएं, लेकिन function signature वही रखें ताकि routes और tests न टूटें।
Enable API और backup codes
setup API secret और QR code बनाता है, लेकिन 2FA तुरंत enable नहीं करता। जब user पहला TOTP सही देता है, तभी backup codes बनते हैं और 2FA चालू होता है। backup codes response में सिर्फ एक बार दिखाएं।
// src/app/api/account/2fa/enable/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { prisma } from "@/lib/prisma";
import { requireUser } from "@/lib/require-user";
import { decryptSecret, generateBackupCodes, hashBackupCode, consumeRateLimit, verifyTotp } from "@/lib/two-factor";
const schema = z.object({ token: z.string().regex(/^\d{6}$/) });
export async function POST(request: NextRequest) {
const user = await requireUser();
const { token } = schema.parse(await request.json());
if (!consumeRateLimit(`2fa-enable:${user.id}`, 5, 10 * 60 * 1000)) {
return NextResponse.json({ error: "Too many attempts." }, { status: 429 });
}
const record = await prisma.user.findUniqueOrThrow({ where: { id: user.id } });
if (!record.twoFactorSecretCiphertext || !record.twoFactorSecretIv || !record.twoFactorSecretTag) {
return NextResponse.json({ error: "2FA setup has not started." }, { status: 400 });
}
const secret = decryptSecret({
ciphertext: record.twoFactorSecretCiphertext,
iv: record.twoFactorSecretIv,
tag: record.twoFactorSecretTag,
});
if (!verifyTotp(token, secret)) {
await prisma.auditLog.create({ data: { userId: user.id, action: "2fa.enable.failed" } });
return NextResponse.json({ error: "Invalid code." }, { status: 400 });
}
const backupCodes = generateBackupCodes();
await prisma.$transaction([
prisma.backupCode.deleteMany({ where: { userId: user.id } }),
prisma.user.update({ where: { id: user.id }, data: { twoFactorEnabled: true, twoFactorEnabledAt: new Date() } }),
prisma.backupCode.createMany({ data: backupCodes.map((code) => ({ userId: user.id, codeHash: hashBackupCode(user.id, code) })) }),
prisma.auditLog.create({ data: { userId: user.id, action: "2fa.enabled", metadata: { backupCodeCount: backupCodes.length } } }),
]);
return NextResponse.json({ backupCodes });
}
login challenge में TOTP या unused backup code स्वीकार करें। backup code सफल हो तो उसी request में usedAt भरें। remembered device के लिए random token बनाएं, database में केवल HMAC save करें, और असली token को httpOnly, secure, sameSite cookie में short expiry और revoke option के साथ भेजें।
UI component
// src/components/TwoFactorSetupPanel.tsx
"use client";
import { useState } from "react";
export function TwoFactorSetupPanel() {
const [qrCodeDataUrl, setQrCodeDataUrl] = useState("");
const [manualKey, setManualKey] = useState("");
const [token, setToken] = useState("");
const [backupCodes, setBackupCodes] = useState<string[]>([]);
const [error, setError] = useState("");
async function startSetup() {
const response = await fetch("/api/account/2fa/setup", { method: "POST" });
const data = await response.json();
if (!response.ok) return setError(data.error ?? "Setup failed.");
setQrCodeDataUrl(data.qrCodeDataUrl);
setManualKey(data.manualKey);
}
async function enable() {
const response = await fetch("/api/account/2fa/enable", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
const data = await response.json();
if (!response.ok) return setError(data.error ?? "Invalid code.");
setBackupCodes(data.backupCodes);
}
return (
<section aria-label="Two-factor authentication">
{!qrCodeDataUrl && <button onClick={startSetup}>Set up 2FA</button>}
{qrCodeDataUrl && backupCodes.length === 0 && (
<>
<img src={qrCodeDataUrl} alt="Authenticator app QR code" width={220} height={220} />
<p>Manual key: <code>{manualKey}</code></p>
<input inputMode="numeric" autoComplete="one-time-code" value={token} onChange={(event) => setToken(event.target.value)} maxLength={6} />
<button onClick={enable}>Verify and enable</button>
</>
)}
{backupCodes.length > 0 && (
<ul>{backupCodes.map((code) => <li key={code}><code>{code}</code></li>)}</ul>
)}
{error && <p role="alert">{error}</p>}
</section>
);
}
Recovery, SMS और passkeys
recovery flow MFA की सबसे कमजोर जगह बन सकता है। पहले backup codes का उपयोग कराएं। अगर सभी factors खो गए हैं, तो email confirmation, existing trusted session, B2B में organization owner approval और high-risk actions से पहले cooldown जोड़ें। recovery के बाद user को notify करें और पुराने sessions revoke करें।
SMS कुछ consumer flows में “कुछ नहीं” से बेहतर हो सकता है, लेकिन SIM swap, number recycling और telecom support social engineering से प्रभावित होता है। admins, billing और API keys के लिए इसे primary factor न बनाएं।
WebAuthn/passkeys लंबी अवधि का बेहतर रास्ता है क्योंकि यह origin-bound public-key credentials का उपयोग करता है। पहले TOTP और backup codes को मजबूत करें, फिर उसी audit, revocation और step-up policy के साथ WebAuthn credential table जोड़ें।
// src/lib/step-up.ts
export function assertFreshMfa(session: { mfaAt?: Date | null }, maxAgeMinutes = 15) {
if (!session.mfaAt) throw new Error("MFA is required.");
if (Date.now() - session.mfaAt.getTime() > maxAgeMinutes * 60 * 1000) {
throw new Error("MFA challenge is too old.");
}
}
उपयोग के उदाहरण और गलतियां
उदाहरण 1: admin invite. owner या admin invite से पहले recent mfaAt मांगें। चोरी हुई session cookie से organization takeover नहीं होना चाहिए।
उदाहरण 2: billing changes. card, invoice email, refund या tax details बदलने पर step-up auth मांगें। audit log में event रखें, card data या backup code नहीं।
उदाहरण 3: API key creation. नई key या बड़ा scope देने से पहले MFA और RBAC दोनों लगाएं, जैसे api_key:create permission।
उदाहरण 4: support recovery. support agent सीधे MFA बंद न करे। request बने, दो-person approval हो, user notification जाए, cooldown हो और audit log बने।
आम गलतियां हैं plaintext TOTP secret, reusable backup codes, 6 digit code पर unlimited attempts, remembered device token raw save करना, 2FA disable करते समय step-up न मांगना, बहुत लंबी recovery links और logs में secrets।
निष्कर्ष
मजबूत 2FA session assurance, recovery, audit और operations का संयुक्त design है। Claude Code छोटे और reviewable tasks में बहुत उपयोगी है, लेकिन encryption keys, permissions और recovery policy की जिम्मेदारी आपकी रहती है। ClaudeCodeLab मौजूदा MFA flow review, Claude Code security training और Next.js products में TOTP से passkeys migration की planning में मदद कर सकता है।
इस implementation को आज़माते समय अलग-अलग environments में अलग keys, backup codes का एक बार display, TOTP failures पर rate limit, remembered device cookie का httpOnly और revocable होना, 2FA disable पर step-up auth, और audit logs में secret values न होना ज़रूर जांचें।
मुफ़्त 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.