Skip to content

Commit f9e35db

Browse files
sng-asyncfuncclaude
andcommitted
docs: prominent disclaimer — research/education only, user is solely responsible
Add a strong disclaimer (README top warning + bottom section, mirrored in SECURITY.md) quoting OpenAI's ToS prohibitions ('reselling access or using ChatGPT to power third-party services', credential sharing, programmatic data extraction). Makes explicit that using this likely violates OpenAI's Terms, that the user is solely responsible, and that the software is provided as-is with no warranty or liability. Also rewrites the README in a cleaner, scannable layout (centered hero, badges, screenshot slots, collapsible reference). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 788e353 commit f9e35db

2 files changed

Lines changed: 172 additions & 146 deletions

File tree

‎README.md‎

Lines changed: 163 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,245 @@
1+
<div align="center">
2+
13
# `<CodexAuth />`
24

3-
Drop a ChatGPT sign-in into any React app. The device-code OAuth flow, the
4-
polling, the session handling: all headless, all yours to style. Your users
5-
bring their own ChatGPT plan (Free, Plus, or Pro); you ship the button.
5+
### Login with ChatGPT, as a React component.
66

7-
An open-source, headless-first React component + npm package. It uses the
8-
official Codex CLI **device-code** flow, proxied through a small backend you
9-
host. **OAuth tokens never touch the browser**: they stay in your backend's
10-
session, the same way the Codex CLI keeps them in `~/.codex/auth.json`.
7+
Your users sign in with their own ChatGPT account and run prompts on it.
8+
**You never pay OpenAI** — they use their own plan.
119

12-
```
13-
npm install codex-auth
14-
```
10+
[![CI](https://github.com/AsyncFuncAI/CodexAuth/actions/workflows/ci.yml/badge.svg)](https://github.com/AsyncFuncAI/CodexAuth/actions/workflows/ci.yml)
11+
[![npm](https://img.shields.io/npm/v/codex-auth.svg)](https://www.npmjs.com/package/codex-auth)
12+
[![license](https://img.shields.io/badge/license-MIT-black.svg)](./LICENSE)
13+
[![React](https://img.shields.io/badge/React-17%2B-black.svg)](https://react.dev)
14+
15+
[Quick start](#quick-start) · [Two runners](#pick-a-runner) · [Headless](#headless-usage) · [Deploy](./DEPLOYMENT.md) · [Security](./SECURITY.md)
16+
17+
> ⚠️ **For research & education only.** This uses OpenAI's Codex client off-label
18+
> and likely violates the ChatGPT Terms of Use. **You are solely responsible for
19+
> your use.** [Read the disclaimer ↓](#%EF%B8%8F-disclaimer--read-before-you-use-this)
20+
21+
</div>
1522

16-
> Requires React 17+. The reference backend requires Node and the official
17-
> [`codex`](https://developers.openai.com/codex) CLI installed on the server.
23+
<p align="center">
24+
<img src="./docs/screenshots/login.png" alt="CodexAuth login screen" width="900" />
25+
</p>
1826

1927
---
2028

21-
## Why this needs a backend (important)
29+
## What it is
2230

23-
`<CodexAuth>` is a **drop-in component for an app that has a backend** — not a
24-
browser-only widget. Here's why, in one paragraph:
31+
A headless React component that adds a **"Login with ChatGPT"** button to your app.
32+
Click it, your user approves on `auth.openai.com`, and you can run prompts on their
33+
ChatGPT plan. The OAuth flow, the popup, the polling, the sessions — all handled.
2534

26-
The whole point is that your users authenticate with their **own ChatGPT
27-
account** and you **never pay OpenAI**. That means real OAuth tokens for their
28-
account exist somewhere. If those tokens lived in the browser, any XSS could
29-
steal them and hijack the user's ChatGPT account. So the security model is
30-
**tokens never touch the browser** — they live on a server you control, which
31-
talks to OpenAI and runs the prompts. The browser only ever sees an `HttpOnly`
32-
cookie and the streamed output.
35+
```bash
36+
npm install codex-auth
37+
```
38+
39+
**OAuth tokens never touch the browser.** They live on a backend you control; the
40+
browser only ever sees an `HttpOnly` cookie and the streamed output.
41+
42+
<p align="center">
43+
<img src="./docs/screenshots/console.png" alt="CodexAuth console running a prompt" width="900" />
44+
</p>
3345

34-
This is exactly how the original login-with-chatgpt works (it runs a server-side
35-
Codex process), and it's the same reason Clerk/Auth0/your-own-DB all need a
36-
backend. **The component gives you the hard 80%** — the popup flow, device-code
37-
UX, polling, sessions, and a security-hardened HTTP contract. **You bring a small
38-
backend endpoint** (one of the drop-in adapters below).
46+
---
47+
48+
## Quick start
3949

40-
### Two runners — pick how the backend talks to OpenAI
50+
**1. Frontend** — drop in the component:
4151

42-
The component is the same; the **runner** decides how your backend reaches OpenAI:
52+
```tsx
53+
import { CodexAuth } from "codex-auth";
4354

44-
| Runner | How it works | Deploys on |
45-
|---|---|---|
46-
| **`directRunner`** (`codex-auth/backend/direct`) | Pure `fetch` — the device-code grant + the Codex `responses` backend directly | **Anywhere, incl. Vercel serverless / edge.** No binary, no persistent process |
47-
| **`defaultCliRunner`** (`codex-auth/backend`) | Shells out to the official `codex` CLI | A **persistent** Node host (Railway, Render, Docker, a VPS) — not Vercel functions |
55+
<CodexAuth onAuthenticated={({ account }) => console.log("hi", account)} />
56+
```
4857

49-
> `directRunner` is the easy default for most apps: it has no native dependency, so
50-
> the whole thing (frontend + backend) can live in one Vercel/Next.js deploy.
51-
> `defaultCliRunner` exists for when you'd rather lean on the installed CLI's auth.
52-
> Both implement the same `CodexRunner` interface and hold tokens server-side.
58+
**2. Backend** — mount one route (Express shown; Next.js below):
5359

5460
```ts
55-
// serverless-friendly: no codex binary needed
5661
import { createCodexRouter } from "codex-auth/backend";
5762
import { directRunner } from "codex-auth/backend/direct";
5863

59-
createCodexRouter({ runner: directRunner(), cookieSecret: process.env.COOKIE_SECRET! });
64+
app.use("/api/codex", createCodexRouter({
65+
runner: directRunner(),
66+
cookieSecret: process.env.COOKIE_SECRET!,
67+
}));
6068
```
6169

62-
See [`DEPLOYMENT.md`](./DEPLOYMENT.md) for Vercel, Railway (one-click), Docker, and
63-
Next.js, and [`SECURITY.md`](./SECURITY.md) for the off-label-API caveats `directRunner`
64-
inherits.
65-
66-
Backend adapters (all share one hardened core, work with either runner):
70+
That's it. The component talks to `/api/codex/*` on the same origin by default.
6771

68-
| Import | For |
69-
|---|---|
70-
| `codex-auth/backend` | Express (`createCodexRouter`) — the generic reference |
71-
| `codex-auth/backend/next` | Next.js App Router route handler |
72-
| `codex-auth/backend/direct` | the serverless-friendly `directRunner` |
73-
| `codex-auth/backend/worker` | Cloudflare Worker — proxy in front of a Node backend |
72+
> **One-time, per user:** they enable *device code authorization* in
73+
> **ChatGPT → Settings → Security & Login**. The component shows this hint
74+
> automatically when needed.
7475
7576
---
7677

77-
## Quick start
78+
## Pick a runner
7879

79-
### 1. Frontend — drop in the component
80+
The component is always the same. The **runner** decides how your backend reaches OpenAI:
8081

81-
```tsx
82-
import { CodexAuth } from "codex-auth";
82+
| Runner | How | Deploys on |
83+
| --- | --- | --- |
84+
| **`directRunner`** | Pure `fetch` to OpenAI. No binary. | **Anywhere — incl. Vercel serverless / edge** |
85+
| **`defaultCliRunner`** | Shells out to the `codex` CLI. | A persistent Node host (Railway, Docker, VPS) |
8386

84-
export function SignIn() {
85-
return <CodexAuth onAuthenticated={({ account }) => console.log("hi", account)} />;
86-
}
87-
```
87+
Both implement the same interface and keep tokens server-side. **`directRunner` is
88+
the recommended default** — frontend + backend can live in one Vercel/Next.js deploy.
8889

89-
That renders the default "Login with ChatGPT" button, the device-code card, the
90-
popup-blocked fallback, and an account console after login.
90+
### Backend adapters
91+
92+
| Import | For |
93+
| --- | --- |
94+
| `codex-auth/backend` | Express — `createCodexRouter` |
95+
| `codex-auth/backend/next` | Next.js App Router route handler |
96+
| `codex-auth/backend/direct` | the serverless-friendly `directRunner` |
97+
| `codex-auth/backend/worker` | Cloudflare Worker proxy |
9198

92-
### 2. Backend — mount the reference router
99+
<details>
100+
<summary><b>Next.js — one file, zero config</b></summary>
93101

94102
```ts
95-
import express from "express";
96-
import { createCodexRouter, defaultCliRunner } from "codex-auth/backend";
97-
98-
const app = express();
99-
app.use(
100-
"/api/codex",
101-
createCodexRouter({
102-
runner: defaultCliRunner(), // shells out to the `codex` CLI
103-
cookieSecret: process.env.COOKIE_SECRET!, // 32-byte random, from env
104-
}),
105-
);
106-
app.listen(8787);
107-
```
108-
109-
The component talks to `/api/codex/*` on the **same origin** by default. That's it.
103+
// app/api/codex/[...codex]/route.ts
104+
import { createNextCodexHandler } from "codex-auth/backend/next";
105+
import { directRunner } from "codex-auth/backend/direct";
110106

111-
### 3. Your users must enable device auth (one time)
107+
export const { GET, POST } = createNextCodexHandler({
108+
runner: directRunner(),
109+
cookieSecret: process.env.COOKIE_SECRET!,
110+
});
111+
```
112112

113-
The first time, a user opens **ChatGPT → Settings → Security & Login** and enables
114-
**device code authorization**. The component shows this hint automatically if the
115-
backend reports `DEVICE_AUTH_NOT_ENABLED`.
113+
</details>
116114

117115
---
118116

119-
## Headless usage (full control of the UI)
117+
## Headless usage
120118

121-
Pass a function as `children` to get the full auth state and render whatever you
122-
want. No default UI is rendered.
119+
Pass a function as `children` for full control — no default UI is rendered:
123120

124121
```tsx
125122
<CodexAuth>
126123
{(auth) =>
127-
auth.isAuthenticated ? (
128-
<button onClick={auth.logout}>Sign out {auth.account}</button>
129-
) : auth.isWaiting ? (
130-
<p>Enter code {auth.userCode} in the popup…</p>
131-
) : (
132-
<button onClick={auth.login}>Login with ChatGPT</button>
133-
)
124+
auth.isAuthenticated
125+
? <button onClick={auth.logout}>Sign out {auth.account}</button>
126+
: <button onClick={auth.login}>Login with ChatGPT</button>
134127
}
135128
</CodexAuth>
136129
```
137130

138-
`useCodexAuth()` exposes the same result if you prefer a hook:
131+
Run a prompt against the user's account once authenticated:
139132

140133
```tsx
141-
const auth = useCodexAuth({ pollIntervalMs: 2500 });
134+
auth.run("Write a haiku about the ocean.", {
135+
onText: (text) => append(text),
136+
onDone: ({ text }) => console.log(text),
137+
});
142138
```
143139

144-
### Running prompts
140+
Prefer a hook? `useCodexAuth()` returns the same `auth` object.
145141

146-
Once authenticated, stream a prompt against the user's account:
142+
---
147143

148-
```tsx
149-
auth.run("Write a haiku about the ocean.", {
150-
onText: (text, mode) => append(text, mode),
151-
onDone: ({ text }) => console.log("final:", text),
152-
onError: (e) => console.error(e),
153-
});
144+
## How it works
145+
146+
```
147+
Browser <CodexAuth/> Your backend OpenAI
148+
│ click login │ │
149+
├── open popup (sync) ─────────┤ │
150+
├── POST /session ────────────▶│ │
151+
├── POST /login/start ────────▶│── device code ───────▶ │
152+
│ show code, poll status │◀── loginUrl + code ────│
153+
│ user approves in popup ───────────────────────────▶ │
154+
├── GET /status (poll) ───────▶│◀── tokens (stay here) ─│
155+
│ authenticated ✓ │ │
156+
├── POST /run/stream ─────────▶│── run prompt ────────▶ │
157+
│ stream output ◀─────────────│◀── tokens never leave ─│
154158
```
155159

156-
Need just the run client without the auth UI? Import from `codex-auth/run`.
160+
The popup is opened **synchronously** on click (so the blocker allows it), then
161+
redirected to the validated `auth.openai.com` URL once the backend returns it.
162+
Full HTTP spec in [`CONTRACT.md`](./CONTRACT.md).
157163

158164
---
159165

160-
## Configuration (`CodexClientConfig`)
166+
## Run the demo
167+
168+
```bash
169+
git clone https://github.com/AsyncFuncAI/CodexAuth
170+
cd CodexAuth && npm install
171+
cp demo/.env.example demo/.env # set COOKIE_SECRET
172+
npm run demo # app → :5173, backend → :8787
173+
```
174+
175+
---
176+
177+
## Reference
178+
179+
<details>
180+
<summary><b><code>CodexClientConfig</code></b> (component / hook options)</summary>
161181

162182
| Option | Default | Purpose |
163-
|---|---|---|
183+
| --- | --- | --- |
164184
| `basePath` | `/api/codex` | Base path for the contract |
165-
| `endpoints` | derived | Per-route overrides (absolute URL or relative path; replaces verbatim) |
166-
| `fetch` | `globalThis.fetch` | Injectable fetch (SSR/tests) |
167185
| `pollIntervalMs` | `3000` | Status poll cadence |
168186
| `resumeMaxAgeMs` | `86_400_000` | Resume a persisted session within 24h |
169-
| `storage` | `localStorage` | Persistence backend; `null` disables (SSR-safe) |
170-
| `credentials` | `same-origin` | fetch credentials mode (see Cross-origin below) |
171-
| `allowedLoginHosts` | `["auth.openai.com"]` | Hosts the login popup may navigate to |
172-
| `enableGravatar` | `false` | Opt-in Gravatar avatar (leaks an email MD5 to gravatar.com) |
187+
| `storage` | `localStorage` | Persistence; `null` disables (SSR-safe) |
188+
| `credentials` | `same-origin` | fetch credentials mode |
189+
| `allowedLoginHosts` | `["auth.openai.com"]` | Hosts the login popup may open |
190+
| `enableGravatar` | `false` | Opt-in Gravatar avatar |
173191

174-
### `useCodexAuth()` result
192+
`useCodexAuth()` returns: `status`, `account`, `userCode`, `loginUrl`, `error`,
193+
`popupBlocked`, `isAuthenticated` / `isConnecting` / `isWaiting`, `avatarUrl`, and
194+
the actions `login()`, `logout()`, `cancelLogin()`, `run()`, `copyUserCode()`.
175195

176-
`status` (`idle` · `resuming` · `connecting` · `waitingForLogin` · `authenticated` ·
177-
`error` · `loggedOut`), `account`, `userCode`, `loginUrl`, `expiresAt`, `error`,
178-
`popupBlocked`, derived `isAuthenticated`/`isConnecting`/`isWaiting`, `avatarUrl`,
179-
and actions `login()`, `logout()`, `cancelLogin()`, `run()`, `copyUserCode()`,
180-
`openLoginPage()`.
196+
</details>
181197

182-
---
183-
184-
## How it works (the popup trick)
198+
<details>
199+
<summary><b>Cross-origin backends</b></summary>
185200

186-
1. On click, the component opens a **blank popup synchronously** — before any
187-
`await` — so the browser's popup blocker permits it, and shows a "Connecting…"
188-
holding screen.
189-
2. It calls your backend (`POST /session`, `POST /login/start`), which runs the
190-
Codex CLI device-code flow and returns a `loginUrl` + `userCode`.
191-
3. The popup is pointed at the (validated) `loginUrl` on `auth.openai.com`. The
192-
user approves; the component polls `GET /status` every few seconds.
193-
4. On success it shows the account. **Tokens stay on your backend the whole time.**
201+
When the app and backend are on different origins, set `credentials: 'include'`
202+
on the client and pass `allowedOrigins: ['https://your-app.example']` to the
203+
router (it emits credentialed CORS for that specific origin, never `*`).
194204

195-
See [`CONTRACT.md`](./CONTRACT.md) for the exact HTTP contract (so you can write a
196-
backend in any language) and [`SECURITY.md`](./SECURITY.md) for the trust model.
205+
</details>
197206

198207
---
199208

200-
## Run the demo
209+
## ⚠️ Disclaimer — read before you use this
201210

202-
```
203-
cp demo/.env.example demo/.env # set COOKIE_SECRET
204-
npm install
205-
npm run demo # backend on :8787, app on :5173
206-
```
211+
> **This project is for education and research. You alone are responsible for how you use it.**
207212
208-
You need the `codex` CLI installed and `codex login --device-auth` available.
213+
`<CodexAuth>` reuses OpenAI's **first-party Codex CLI** OAuth client and talks to
214+
undocumented Codex endpoints off-label. **This very likely violates OpenAI's Terms
215+
of Use.** OpenAI's ChatGPT plans explicitly prohibit, among other things:
209216

210-
---
217+
> - *Abusive usage, such as automatically or programmatically extracting data.*
218+
> - *Sharing your account credentials or making your account available to anyone else.*
219+
> - **Reselling access or using ChatGPT to power third-party services.**
220+
>
221+
> — [OpenAI Help Center, "Access and usage allowances"](https://help.openai.com/en/articles/8983719-what-is-chatgpt-pro)
211222
212-
## Cross-origin backends
223+
Using this to power an app for other users falls squarely under that last point.
213224

214-
Default `credentials` is `same-origin` — keep it that way when the app and backend
215-
share an origin (recommended). For a backend on a **different** origin:
225+
**By using this software, you acknowledge that:**
216226

217-
- set `credentials: 'include'` on the client, **and**
218-
- pass `allowedOrigins: ['https://your-app.example']` to `createCodexRouter` so it
219-
emits credentialed CORS for that **specific** origin (never `*`).
227+
- You are **solely responsible** for ensuring your use complies with OpenAI's
228+
[Terms of Use](https://openai.com/policies/row-terms-of-use/) and Usage Policies.
229+
- It may get **your account, your users' accounts, or the shared OAuth client
230+
rate-limited, suspended, or banned**, at OpenAI's discretion.
231+
- The undocumented endpoints can **change or break at any time** with no notice.
232+
- The authors provide this **as-is, with no warranty**, and accept **no liability**
233+
for any consequence of its use (see the [MIT License](./LICENSE)).
220234

221-
Never reflect an arbitrary `Origin` with credentials — see `SECURITY.md`.
235+
**Do not build a product or business on this.** For anything real, use the
236+
[official OpenAI API](https://platform.openai.com) with your own key, or have each
237+
user bring their own key. See [`SECURITY.md`](./SECURITY.md) for the full trust model.
222238

223239
---
224240

225-
## License
241+
<div align="center">
242+
243+
**MIT** · Not affiliated with or endorsed by OpenAI · For research & education only
226244

227-
MIT. Not affiliated with OpenAI.
245+
</div>

0 commit comments

Comments
 (0)