Claude Code से OAuth लागू करें: PKCE, state, nonce और सुरक्षित token storage
OAuth 2.1/2.0 Authorization Code + PKCE को Claude Code से सुरक्षित तरीके से लागू करने की शुरुआती गाइड।
OAuth सिर्फ “Google से login” बटन लगाने का नाम नहीं है। छोटी गलती से गलत account जुड़ सकता है, authorization code दोबारा इस्तेमाल हो सकता है, redirect URI बदला जा सकता है, या token browser JavaScript से पढ़ा जा सकता है। यह guide बताती है कि Claude Code से OAuth scaffold कैसे कराएं, बिना असली secrets दिए, और फिर उसे security feature की तरह कैसे review करें।
Practical default OAuth 2.0 Authorization Code + PKCE है, जो OAuth 2.1 की दिशा से मेल खाता है। PKCE में app login शुरू करते समय challenge भेजती है और token लेते समय साबित करती है कि original verifier उसी के पास है। Background के लिए Claude Code authentication, Claude Code API development, और getting started guide देखें।
सही implementation shape
Claude Code को routes, session, tests और review checklist बनाने दें। Real client secret, refresh token, production .env, या provider console screenshot paste न करें। केवल variable names, expected behavior और failure cases दें।
| Area | Recommendation | Reason |
|---|---|---|
| Flow | Authorization Code + PKCE | पुराने implicit flow जैसी token exposure से बचता है |
| CSRF | state save और verify करें | forged callback रोकता है |
| OIDC replay | nonce save और verify करें | reused identity result पकड़ता है |
| Redirect URI | Exact allow-list match | destination बदलने से रोकता है |
| Tokens | Server-side session या encrypted store | लंबे tokens को localStorage से बाहर रखता है |
| Claude Code input | Dummy values और specs | prompts/logs में secrets नहीं जाते |
Primary references: OAuth 2.1, RFC 7636 PKCE, RFC 9700 OAuth 2.0 Security BCP, OpenID Connect Core, OWASP OAuth2 Cheat Sheet, Claude Code Security।
तीन real use cases
पहला use case B2B SaaS admin console है। User Google Workspace या Microsoft Entra ID से login करता है, लेकिन app उसे अपने internal user, organization और role में map करती है। OAuth identity बताता है; permission आपकी app तय करती है।
दूसरा delegated API access है। Calendar, mail, storage और CRM integrations में user consent, छोटे scopes, encrypted refresh tokens और disconnect/revoke flow चाहिए।
तीसरा internal tools और MCP-style services हैं। Static API keys शुरुआत में आसान लगती हैं, लेकिन revoke और audit कठिन हो जाते हैं। OAuth/OIDC login, consent, expiry और identity boundary साफ करता है।
Mobile apps और SPA client secret छिपा नहीं सकते, इसलिए PKCE जरूरी है। Server-side web apps में भी PKCE review को मजबूत बनाता है।
Copy-paste runnable local demo
इस demo को Google, Microsoft या Auth0 credentials नहीं चाहिए। एक Express process OAuth client और mock authorization server दोनों बनता है, ताकि state, nonce, PKCE S256, exact redirect validation, one-time code और server-side token storage देख सकें।
Empty folder में ये files बनाएं, npm install && npm start चलाएं, फिर http://localhost:3000 खोलें।
{
"scripts": { "start": "node server.mjs" },
"dependencies": { "express": "^4.19.2", "express-session": "^1.18.0" },
"engines": { "node": ">=20" }
}
// server.mjs
import crypto from "node:crypto";
import express from "express";
import session from "express-session";
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(session({
name: "oauth_demo_sid",
secret: "dev-only-change-this-32-byte-secret",
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, sameSite: "lax", secure: false, maxAge: 10 * 60 * 1000 },
}));
const client = {
clientId: "claude-code-demo",
redirectUri: "http://localhost:3000/callback",
scope: "openid profile email",
};
const authorizationEndpoint = "http://localhost:3000/mock/authorize";
const tokenEndpoint = "http://localhost:3000/mock/token";
const registeredRedirectUris = new Set([client.redirectUri]);
const pendingCodes = new Map();
function randomUrlSafe(bytes = 32) {
return crypto.randomBytes(bytes).toString("base64url");
}
function sha256Base64Url(value) {
return crypto.createHash("sha256").update(value).digest("base64url");
}
function fail(res, status, message) {
return res.status(status).type("text/plain").send(message);
}
app.get("/", (_req, res) => {
res.type("html").send(`<h1>OAuth PKCE local demo</h1><p><a href="/auth/login">Start login</a></p>`);
});
app.get("/auth/login", (req, res) => {
const state = randomUrlSafe();
const nonce = randomUrlSafe();
const codeVerifier = randomUrlSafe(48);
const codeChallenge = sha256Base64Url(codeVerifier);
req.session.oauth = { state, nonce, codeVerifier, createdAt: Date.now() };
const params = new URLSearchParams({
response_type: "code",
client_id: client.clientId,
redirect_uri: client.redirectUri,
scope: client.scope,
state,
nonce,
code_challenge: codeChallenge,
code_challenge_method: "S256",
});
res.redirect(`${authorizationEndpoint}?${params}`);
});
app.get("/mock/authorize", (req, res) => {
const p = req.query;
const redirectUri = String(p.redirect_uri || "");
if (p.response_type !== "code") return fail(res, 400, "response_type must be code");
if (p.client_id !== client.clientId) return fail(res, 400, "unknown client_id");
if (!registeredRedirectUris.has(redirectUri)) return fail(res, 400, "redirect_uri is not registered exactly");
if (p.code_challenge_method !== "S256") return fail(res, 400, "PKCE S256 is required");
if (!p.code_challenge || !p.state || !p.nonce) return fail(res, 400, "missing state, nonce, or PKCE challenge");
const code = randomUrlSafe(24);
pendingCodes.set(code, {
clientId: client.clientId,
redirectUri,
codeChallenge: String(p.code_challenge),
nonce: String(p.nonce),
expiresAt: Date.now() + 60_000,
used: false,
});
const redirect = new URL(redirectUri);
redirect.searchParams.set("code", code);
redirect.searchParams.set("state", String(p.state));
res.redirect(redirect.toString());
});
app.get("/callback", async (req, res) => {
const oauth = req.session.oauth;
const code = String(req.query.code || "");
const returnedState = String(req.query.state || "");
if (!oauth) return fail(res, 400, "missing OAuth session");
if (returnedState !== oauth.state) return fail(res, 403, "state mismatch: possible CSRF or mixed login attempt");
const response = await fetch(tokenEndpoint, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: client.redirectUri,
client_id: client.clientId,
code_verifier: oauth.codeVerifier,
}),
});
const tokens = await response.json();
if (!response.ok) return fail(res, response.status, JSON.stringify(tokens, null, 2));
if (tokens.nonce !== oauth.nonce) return fail(res, 403, "nonce mismatch: possible replay");
req.session.oauth = undefined;
req.session.tokenSet = {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: Date.now() + tokens.expires_in * 1000,
};
res.redirect("/dashboard");
});
app.post("/mock/token", (req, res) => {
const body = req.body;
const record = pendingCodes.get(body.code);
if (body.grant_type !== "authorization_code") return res.status(400).json({ error: "unsupported_grant_type" });
if (!record || record.used || record.expiresAt < Date.now()) return res.status(400).json({ error: "invalid_grant" });
if (body.client_id !== record.clientId) return res.status(400).json({ error: "invalid_client" });
if (body.redirect_uri !== record.redirectUri) return res.status(400).json({ error: "invalid_redirect_uri" });
if (sha256Base64Url(body.code_verifier || "") !== record.codeChallenge) return res.status(400).json({ error: "invalid_code_verifier" });
record.used = true;
res.json({ token_type: "Bearer", access_token: randomUrlSafe(32), refresh_token: randomUrlSafe(32), expires_in: 300, nonce: record.nonce });
});
app.get("/dashboard", (req, res) => {
const tokenSet = req.session.tokenSet;
if (!tokenSet) return res.redirect("/auth/login");
const secondsLeft = Math.max(0, Math.floor((tokenSet.expiresAt - Date.now()) / 1000));
res.type("html").send(`<h1>Logged in</h1><p>Access token is stored server-side, not in localStorage.</p><p>Expires in ${secondsLeft} seconds.</p>`);
});
app.listen(3000, () => console.log("Open http://localhost:3000"));
मुख्य बातें: /auth/login server session में state, nonce, code_verifier रखता है; request केवल code_challenge भेजती है; /callback state verify करता है; /mock/token S256 दोबारा calculate करता है; /dashboard token server session से पढ़ता है, localStorage से नहीं।
Claude Code prompt
Express + TypeScript में OAuth 2.0 Authorization Code + PKCE implement करें।
Requirements:
- Provider settings सिर्फ environment variable names के रूप में define करें; real secrets न लिखें।
- state, nonce, code_verifier server-side session में store करें।
- redirect_uri को configured value से exact match करें।
- केवल code_challenge_method=S256 allow करें।
- access token या refresh token को localStorage में store न करें।
- refresh token केवल encrypted database field या server-side session में रखें।
- success, state mismatch, PKCE mismatch, expired code, code reuse tests जोड़ें।
- अंत में security review checklist दें।
Review prompt:
इस OAuth implementation को RFC 9700, RFC 7636 और OWASP OAuth2 Cheat Sheet के against review करें।
Logs, test snapshots, frontend bundles और Git diff में secrets हैं या नहीं जांचें।
Findings को High/Medium/Low में classify करें और patches suggest करें।
Common pitfalls
Redirect URI को prefix से validate न करें। https://app.example.com.evil.test/callback आपकी app नहीं है। Registered URI से exact compare करें।
state generate करके छोड़ न दें। Login start पर save करें, callback पर compare करें, फिर delete करें। Multiple tabs support करने हों तो state-keyed short transactions रखें।
PKCE plain avoid करें। S256 allow करें, verifier log न करें, codes short-lived और one-time रखें।
OIDC में सिर्फ nonce काफी नहीं है। JWT signature, issuer, audience, expiry और nonce validate करें। ID Token identity है; Access Token API authorization है। इन्हें mix करना trust bug बनाता है।
Long-lived tokens को localStorage में न रखें। Web apps में server-side sessions, encrypted token tables, short cookie lifetime और revoke flow बेहतर हैं। Cookie details के लिए Claude Code cookie management देखें।
Professional delivery checklist
- Token lifetime और refresh behavior documented है।
- Team
state,nonce, PKCE को सरल भाषा में समझा सकती है। - Redirect URIs, scopes और provider settings recorded हैं।
- Logs में code, verifier, token या cookie value नहीं है।
- Error messages internal details expose नहीं करते।
- Tests success, mismatch, expiry और reuse cover करते हैं।
- Claude Code output official specs से review किया गया है।
ClaudeCodeLab training और consultation इसे team workshop में बदल सकते हैं: existing OAuth flow लाएं, risks map करें, tests जोड़ें और reusable review checklist लेकर जाएं। Format के लिए ClaudeCodeLab training देखें।
Hands-on verification
Demo login start, mock authorization, callback, token exchange और dashboard तक चलता है। Returned state बदलने पर state mismatch आता है। गलत verifier देने पर invalid_code_verifier मिलता है। OAuth prototypes में happy path कम, multiple tabs, back button, expired code और accidental logs ज्यादा छूटते हैं। इन्हें पहले Claude Code prompt में लिखें।
Summary
OAuth quality login button से नहीं, पूरी transaction की सुरक्षा से आती है। Authorization Code + PKCE अपनाएं, state और nonce verify करें, redirect URI exact match करें और tokens server-side रखें। Claude Code speed देता है, लेकिन clear constraints और primary-source review जरूरी हैं।
मुफ़्त 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.