Skip to main content
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:
Do not forward anything else from the browser: no 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

Run it with an ASGI or threaded server so open event streams do not block other requests. The ROUTES patterns cover the allowlist; the body rules in the table still need their few lines of checks.