Skip to content

Instantly share code, notes, and snippets.

@kowyo
Created June 11, 2026 10:55
Show Gist options
  • Select an option

  • Save kowyo/67c25df2f0f463778b7bcbd27b61b8e8 to your computer and use it in GitHub Desktop.

Select an option

Save kowyo/67c25df2f0f463778b7bcbd27b61b8e8 to your computer and use it in GitHub Desktop.
Minimal OpenAI Codex OAuth PKCE flow demo
import http from "node:http";
import crypto from "node:crypto";
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
const REDIRECT_URI = "http://localhost:1455/auth/callback";
const TOKEN_URL = "https://auth.openai.com/oauth/token";
async function generatePKCE() {
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
return { verifier, challenge };
}
function startCallbackServer(state: string): Promise<string> {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
const url = new URL(req.url!, "http://localhost");
if (url.pathname !== "/auth/callback") { res.writeHead(404).end(); return; }
if (url.searchParams.get("state") !== state) { res.writeHead(400).end(); return; }
const code = url.searchParams.get("code");
if (!code) { res.writeHead(400).end(); return; }
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
.end("<h1>登录成功,可以关闭此窗口</h1>");
server.close();
resolve(code);
});
server.listen(1455, "127.0.0.1");
server.on("error", (err: NodeJS.ErrnoException) => {
if (err.code === "EADDRINUSE") reject(new Error("端口 1455 被占用"));
else reject(err);
});
});
}
async function exchangeCode(code: string, verifier: string) {
const resp = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: CLIENT_ID, code, code_verifier: verifier, redirect_uri: REDIRECT_URI,
}),
});
if (!resp.ok) throw new Error(`Token exchange failed (${resp.status})`);
return resp.json() as Promise<{ access_token: string; refresh_token: string; expires_in: number }>;
}
function extractAccountId(accessToken: string): string | null {
try {
const payload = JSON.parse(Buffer.from(accessToken.split(".")[1]!, "base64").toString());
return payload?.["https://api.openai.com/auth"]?.chatgpt_account_id ?? null;
} catch { return null; }
}
async function main() {
const { verifier, challenge } = await generatePKCE();
const state = crypto.randomBytes(16).toString("hex");
const authUrl = new URL("https://auth.openai.com/oauth/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
authUrl.searchParams.set("scope", "openid profile email offline_access");
authUrl.searchParams.set("code_challenge", challenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("state", state);
authUrl.searchParams.set("codex_cli_simplified_flow", "true");
console.log("\n=== OpenAI Codex OAuth ===\n");
const codePromise = startCallbackServer(state);
const open = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
Bun.spawnSync([open, authUrl.toString()]);
console.log(`如果浏览器未自动打开:\n ${authUrl}\n`);
const code = await codePromise;
const token = await exchangeCode(code, verifier);
const accountId = extractAccountId(token.access_token);
console.log(`accountId: ${accountId ?? "提取失败"}`);
console.log(`access_token: ${token.access_token}`);
console.log(`refresh_token: ${token.refresh_token}`);
console.log(`expires_in: ${token.expires_in}s (${(token.expires_in / 3600).toFixed(1)}h)`);
console.log("\n验证 token...");
const testResp = await fetch("https://chatgpt.com/backend-api/models", {
headers: { Authorization: `Bearer ${token.access_token}`, "chatgpt-account-id": accountId ?? "" },
});
console.log(` -> ${testResp.status} ${testResp.statusText}`);
if (testResp.ok) {
const data = await testResp.json() as { models?: Array<{ slug: string }> };
console.log(`可用模型: ${(data.models ?? []).map((m) => m.slug).join(", ")}`);
}
console.log("\n完成。保存好 access_token / refresh_token / accountId 后续使用。");
}
main().catch((err) => { console.error(err.message); process.exit(1); });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment