- Python 98.3%
- Dockerfile 1.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| app | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| compose.yaml | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
ChatGPT OAuth Proxy for LibreChat
Small proof-of-concept OpenAI-compatible proxy for LibreChat. It translates POST /v1/chat/completions requests into the public OpenAI Responses API using the officially supported Sign in with ChatGPT flow for open-source apps.
It uses a ChatGPT subscription's eligible plan usage, not a normal OpenAI API key. It does not use browser cookies, ChatGPT private endpoints, session tokens, scraped credentials, or another app's OAuth client credentials.
Architecture
LibreChat -> http://chatgpt-oauth-proxy:8080/v1 -> public /v1/responses
^
ChatGPT-plan OAuth access token
OpenAI requires store: false and stream: true for this flow. The proxy therefore always streams upstream. For a stream: false Chat Completions request it consumes that supported stream and returns a regular Chat Completions object; for stream: true it translates events without buffering the completion.
Official behavior verified
Implementation follows these current official documents:
- OpenAI OSS Sign in with ChatGPT overview
- Registration and PKCE sign-in
- Models and inference
- Remote/self-hosted VM credentials
- Preview limitations
- LibreChat custom endpoints
Important restrictions from OpenAI:
- OAuth is dynamic public-client registration using
client_id=dynamic_agent_client, PKCE, a savedext_agent_host_id, and no client secret/API key. - The callback must be
http://127.0.0.1:<port>/auth/callback;localhostand a remote callback are not supported. /v1/modelsreturns the signed-in account's catalog and only models withvisibility: list.- Inference uses only
https://api.openai.com/v1/responses, never ChatGPTbackend-apiendpoints. - HTTP requests must include complete history in
input;previous_response_id, persistent conversations,temperature,top_p, and many other Responses options are unsupported.
Quick start
- Create a Docker network shared with LibreChat, if needed:
docker network create librechat
-
Create
.envfrom.env.exampleand set a long randomPROXY_SHARED_SECRET. For remote workstation setup, setPROXY_BIND_ADDRESSto the Docker host's private-LAN IP. The default published port is18080, avoiding a common HTTPS reverse-proxy collision on port8080. This proxy itself serves HTTP only and should remain private. -
Build and start the proxy:
docker compose up --build -d
- Query the host ID. This is not secret; it must be preserved when credentials are imported. If
ADMIN_SECRETis configured, send it as a bearer token. Without it, management status is intentionally available only on the private Docker/LAN network.
curl http://SERVER:18080/admin/auth/status
- On the workstation with the browser, clone this project or install its Python dependencies, then run the local loopback login. Substitute the host ID returned above. This opens only the official OpenAI authorization page and receives the callback on the workstation's
127.0.0.1.
python -m pip install -e '.[dev]'
chatgpt-oauth-login --host-id 'urn:uuid:SERVER_HOST_ID' --credentials ./credentials.json
- Transfer that protected file through SSH to the running container volume. This is the exact workflow OpenAI documents for a self-hosted VM: login locally, transfer the selected registration's credential file securely, and keep the VM host ID.
docker compose cp ./credentials.json chatgpt-oauth-proxy:/data/credentials.json
docker compose restart chatgpt-oauth-proxy
The container automatically replaces the imported file's host ID with the ID persisted in /data/host_id.txt, so an imported workstation identity cannot overwrite the server host identity. The credential file is mode 0600; updates use write-then-atomic-replace so refresh-token rotation is saved together.
Confirm login and model discovery:
curl -H 'Authorization: Bearer YOUR_PROXY_SHARED_SECRET' http://SERVER:18080/v1/models
curl -N http://SERVER:18080/v1/chat/completions \
-H 'Authorization: Bearer YOUR_PROXY_SHARED_SECRET' \
-H 'Content-Type: application/json' \
-d '{"model":"MODEL_FROM_MODELS","stream":true,"messages":[{"role":"user","content":"Say hello."}]}'
LibreChat configuration
LibreChat's current custom-endpoint documentation requires name, apiKey, baseURL, and models; fetch: true tells it to use this proxy's /v1/models response. Add this to the mounted librechat.yaml:
version: 1.3.17
endpoints:
custom:
- name: 'ChatGPT Plan'
apiKey: '${CHATGPT_PROXY_SECRET}'
baseURL: 'http://chatgpt-oauth-proxy:8080/v1'
models:
fetch: true
default: []
titleConvo: true
titleModel: 'current_model'
dropParams: ['temperature', 'top_p', 'max_tokens', 'stop']
modelDisplayLabel: 'ChatGPT Plan'
Add CHATGPT_PROXY_SECRET with the same value as PROXY_SHARED_SECRET to LibreChat's .env, mount librechat.yaml into its API container as documented by LibreChat, then restart LibreChat. The proxy container and LibreChat API container must share the librechat Docker network.
API and compatibility
GET /health: unauthenticated health and OAuth availability.GET /v1/models: account-specific supported models.POST /v1/chat/completions: text system/developer, user, assistant, and complete multi-turn history;stream: trueSSE translation and a practicalstream: falseresponse.GET /admin/auth/status: host ID and login state. Protect it withADMIN_SECRETif it is reachable beyond the private network.
The proxy returns OpenAI-style JSON errors for an invalid local key, absent or expired credentials, refresh failure, malformed input, models/inference upstream errors, and timeouts. It logs request IDs, endpoint, status, model discovery/inference failures, refresh events, and latency. It never logs tokens or request contents.
Limitations
- This is single-account and single-host PoC storage. It intentionally does not expose a multi-user account picker.
- OpenAI's SIWC HTTP preview does not allow
temperature,top_p,max_output_tokens, persistent Responses conversations, background jobs, or hosted tools such as image generation, file search, Code Interpreter, hosted MCP, or computer use. - The OpenAI Chat Completions flat
toolsshape is rejected. SIWC supports function/custom tools only with Responses namespaces oradditional_toolsinput items, which cannot be faithfully inferred from a flat Chat Completions request. Assistant tool-call history is accepted for continuing an existing transcript, but this facade does not advertise tools to a model. - Only text content is translated. Although SIWC may support images/files for an eligible model, this focused LibreChat facade does not implement multipart/file handling.
- A Plus account's five-hour plan limit is shared across apps; an upstream usage limit can arrive after streaming begins and is emitted as an SSE error followed by
[DONE]. - Do not expose the inference or admin endpoint publicly. Keep it on the Docker/LAN network behind LibreChat/Pangolin as described.
Operations
Inspect logs:
docker compose logs -f chatgpt-oauth-proxy
Restarting preserves /data and authentication. Access tokens refresh automatically just before expiry using the issued dynamic client ID; a returned refresh token replaces the old token atomically.
To reauthenticate, repeat the local login/import workflow. To remove local access, stop the proxy and delete credentials.json from its /data volume. For complete remote revocation, disconnect the app in ChatGPT Settings; OpenAI's documented revocation endpoint requires the saved refresh token and issued client ID, which this focused PoC does not expose as an HTTP operation to avoid placing renewal credentials on the network.
Development
python -m pip install -e '.[dev]'
pytest
docker compose build
Tests mock OAuth and cover conversion, SSE translation, atomic credential storage, refresh-token rotation, invalid local API keys, and health. They do not require a ChatGPT account.