Security model
What a signed-in app can and cannot do, which guardrails ship by default, and where your obligations start.
Login with ChatGPT stores refreshable credentials for a ChatGPT account and can spend that user's plan. Treat the handler as usage-sensitive infrastructure, not a harmless social login button.
The threat model, honestly
This SDK is open source and runs on the app developer's own server. There is no hosted service in the middle. That means the SDK's guardrails protect users from buggy or careless integrations, not from a malicious developer, who controls the server and can change the code. Three things actually bound the damage:
The SDK only uses the session for model listing and AI requests. The OAuth session belongs to the public Codex client and the SDK routes it to the ChatGPT-backed Codex endpoints it supports. Do not promise users tighter access boundaries than OpenAI grants at the token level. A malicious server controls its own code, can spend the user's plan through supported AI requests, and can read the profile fields in the id token (email, name, plan).
The user's consent is the real gate. The default widget shows a usage-risk warning before login, always. It can be customized but not disabled.
Sessions are revocable. Signing out deletes the app's stored session, and the app keeps nothing that works afterward.
User consent
The default <LoginWithChatGPT /> widget shows a consent step before
anything touches OpenAI: a tab/window, or inline when the window is blocked. It
tells users that requests use their own ChatGPT plan, that prompts and
attachments pass through your server, that the app never sees their password
and that this SDK uses the session for model listing and AI requests, and
that signing out deletes the stored session.
If you build a custom UI with useLoginWithChatGPT() or a children
renderer, that obligation moves to you. Render equivalent consent before
calling login(). Keep the copy short and specific. "I trust this app,
continue" is better than a vague "Connect".
Default guardrails
On without any configuration:
- In the default configuration, refresh tokens never leave the session layer.
auth.getTokens(request)/auth.dangerouslyGetTokens(request)are disabled unless the handler is configured withdangerouslyAllowTokenExport: true. - Normal app code can use
/responses,/models, orauth.proxyFetch(request)without receiving raw bearer tokens. - The
/responsesproxy rate limits each session at 30 requests/minute, so runaway client code cannot quietly burn a user's plan. - Cookie-authenticated non-GET routes reject cross-origin browser requests
unless the origin is in
allowedOrigins. Third-party sites cannot ride the session cookie, even withSameSite=None. - Tokens are AES-GCM encrypted at rest when
secretis set. - Sessions that never authenticate expire from the store in ~30 minutes, so
unauthenticated
POST /logincalls cannot pile up long-lived entries.
Your guardrails
The flow has no narrow scopes. There is no "read-only", "one request", or "max spend" grant, so once authorized, your server can proxy requests until the session expires or is deleted. Layer product-level limits on top:
- Restrict models with
responsesProxy.allowedModels, and tightenresponsesProxy.rateLimitwith a shared store across instances. - Cap prompt length, output length, and attachment size at your app boundary.
- Keep
/responsesthe only spend path. Do not expose a generic OpenAI proxy. - Do not enable
dangerouslyAllowTokenExportunless the application server is intentionally trusted to hold raw ChatGPT bearer tokens. - Use a short
sessionTtlMsfor risky or public apps. - Add an emergency kill switch in front of the proxy route.
- Log request metadata for abuse review, not prompts or file contents.
- Rate limit
/loginand/statusat the edge (WAF, CDN, or gateway). They are unauthenticated by design, because no session exists yet, so the handler cannot see a trustworthy client IP to limit them itself. Without an edge limit, a script can drive repeated upstream device-code requests against your OpenAI client.
Sign-out and cleanup
Give users a visible sign-out that calls the logout route. It deletes the stored session server-side, and it is the most reliable way to remove this app's credentials: OpenAI's active-sessions UI does not necessarily list third-party or device-code sessions. For users who want to review their account anyway, link: