OAuth device login for ChatGPT accounts
Let users bring their own ChatGPT to your app.
Server-owned login, encrypted sessions, and Vercel AI SDK streaming on the user's plan — no API key from you, no tokens in the browser.
terminal
bun add @opencoredev/loginwithchatgpt-server \
@opencoredev/loginwithchatgpt-react @opencoredev/loginwithchatgpt-aiserver.tsthe whole backend
const auth = createChatGPTHandler({
secret: process.env.LWC_SECRET,
});
Bun.serve({
routes: {
"/api/chatgpt/*": (req) => auth.handler(req),
},
});One handler owns the whole flow
The device-code flow needs no redirect URL, so it works the same in serverless, containers, and local dev.
- 01
Browser starts login
The widget shows a consent step, then requests a device code.
POST /api/chatgpt/login - 02
User authorizes on OpenAI
They enter a short code on auth.openai.com — no redirect URL, no localhost listener.
auth.openai.com/codex/device - 03
Server stores the tokens
Polling advances one step per request; tokens are encrypted into your session store.
GET /api/chatgpt/status - 04
AI SDK streams through you
streamText() hits your proxy, which injects credentials and streams back.
POST /api/chatgpt/responses
The browser never holds a token
What the browser sees
- HttpOnly session cookie
- an opaque signed id — no tokens, ever
- The user code
- shown once during device authorization
- Public profile
- email, name, and plan from the id token
- Streamed text
- responses relayed by your proxy route
What your server keeps
- Access & refresh tokens
- AES-GCM encrypted in your session store
- Token refresh
- automatic, deduplicated, 60s early margin
- Rate limits & allowlists
- 30 req/min per session by default
- Origin checks
- cross-site requests can't ride the cookie
Signed-in apps can spend the user's plan, so consent is mandatory and guardrails are on by default. Read the security model.

