createSessionProxyHandler is JavaScript. A Django, Rails, Go, PHP or Java
backend does not need a Node sidecar: implement the same small contract in its
own web framework and point the unmodified browser SDK (OpenGeniClient with
baseUrl: "/api/opengeni") and @opengeni/react at it.
The proxy forwards an exact allowlist of routes to the OpenGeni API, adds
the organization key and the signed-in user’s identity, and removes everything
the browser must not choose. Anything else is a 404.
Routes to forward
Paths are relative to the mount (/api/opengeni) and to /v1/ upstream.
{ws} must equal the workspace your server resolved for this user; {sid} is
any session id (OpenGeni checks the user may read it; add your own check if
your product restricts sessions further).
Message rules (send, steer, draft, submit): reject
mcpCredentialUpdates;
resources may only contain { "kind": "file", ... }; drop model,
reasoningEffort and latencyMode if your product fixes the model. Your server
may add modelContext (page state) and mcpCredentialUpdates (fresh tool
tokens) before forwarding.
Artifact and Site viewing (the JS handler’s opt-in artifacts: true) is not in
this list: it needs a per-user cache partition in config/client, live
tickets, and a session-scope check on every read (x-opengeni-session-id plus
GET /v1/workspaces/{ws}/sessions/{sid}/artifact-associations/{id}). Leave it
out, and link artifacts to your own authenticated pages with resolveLink.
Get the user’s subjectId once per user from GET /v1/access/me with the
headers below, and cache it.
Headers
Add on every upstream request:Cookie, Authorization,
or other x-opengeni-* headers. Return upstream status codes and JSON error
bodies unchanged; they carry codes the SDK understands.
Security rules
- Authenticate the product user on every request and derive the workspace and user id on the server. Never read them from the path, query or body.
- Reject any
{ws}other than the resolved one (403). - Keep the allowlist exact: route and method. Never forward arbitrary paths under the organization key.
- Apply your CSRF protection to POST, PUT and PATCH.
- Bound request bodies (1 MiB is plenty) and do not log bodies or keys.
- Stream SSE without buffering (
text/event-stream, no compression, flush each event) and abort the upstream request when the browser disconnects. - The user must already be a workspace member (onboarding with
addExternalWorkspaceMember); the proxy never grants membership.
Django example
ROUTES patterns cover the allowlist; the body rules in the table
still need their few lines of checks.