Trusted Upstream Browser Auth
Use trusted upstream auth when MindRoom API and browser routes sit behind a deployment-owned access layer that has already authenticated the human. This mode is disabled by default. Do not enable it unless the reverse proxy or identity gateway strips client-supplied copies of the trusted headers and injects verified values itself. Header-only mode is a compatibility option for deployments where MindRoom is only reachable through that trusted gateway. Prefer strict JWT mode when the gateway can provide a signed upstream assertion.
Why It Exists
Agent-issued OAuth links are normal browser links such as /api/oauth/google_drive/authorize?connect_token=....
The connect token records the Matrix requester that triggered the missing-credentials tool result.
In a hosted multi-user private-agent deployment, the browser opening that link must authenticate as the same requester.
The standalone MINDROOM_OWNER_USER_ID setting maps every dashboard request to one Matrix user, so it is only appropriate for single-owner deployments.
It is not a hosted multi-user identity solution.
Environment
Configure the header names that your access layer owns:
MINDROOM_TRUSTED_UPSTREAM_AUTH_ENABLED=true
MINDROOM_TRUSTED_UPSTREAM_USER_ID_HEADER=X-MindRoom-User-Id
MINDROOM_TRUSTED_UPSTREAM_EMAIL_HEADER=X-MindRoom-User-Email
MINDROOM_TRUSTED_UPSTREAM_MATRIX_USER_ID_HEADER=X-MindRoom-Matrix-User-Id
MINDROOM_TRUSTED_UPSTREAM_EMAIL_TO_MATRIX_USER_ID_TEMPLATE='@{localpart}:example.org'
MINDROOM_TRUSTED_UPSTREAM_EMAIL_DOMAIN=example.com
MINDROOM_TRUSTED_UPSTREAM_USER_ID_HEADER is required when trusted upstream auth is enabled.
The user ID value must be stable for the authenticated browser user.
MINDROOM_TRUSTED_UPSTREAM_EMAIL_HEADER is optional unless header-only auth uses MINDROOM_TRUSTED_UPSTREAM_EMAIL_TO_MATRIX_USER_ID_TEMPLATE.
When the email-to-Matrix template is set without strict JWT mode, MINDROOM_TRUSTED_UPSTREAM_EMAIL_HEADER is required because MindRoom derives the Matrix localpart from that trusted email value.
When present, the email value is stored in request.scope["auth_user"]["email"].
MINDROOM_TRUSTED_UPSTREAM_MATRIX_USER_ID_HEADER is optional for shared dashboard access.
For private user and user_agent OAuth flows, the trusted identity must resolve to the requester identity used by Matrix-backed tool execution.
Prefer MINDROOM_TRUSTED_UPSTREAM_MATRIX_USER_ID_HEADER when your access layer can supply a real Matrix ID.
When the access layer only supplies email, set MINDROOM_TRUSTED_UPSTREAM_EMAIL_TO_MATRIX_USER_ID_TEMPLATE to derive the Matrix ID from the trusted email localpart.
Set MINDROOM_TRUSTED_UPSTREAM_EMAIL_DOMAIN to the single allowed email domain before upgrading a deployment that derives Matrix identities from email.
Missing domain configuration fails closed; emails from any other domain, including subdomains, are rejected.
Domain comparison is case-insensitive.
For example, with email domain example.com, the template @{localpart}:example.org maps alice@example.com to @alice:example.org.
The template must contain exactly one {localpart} placeholder.
Derived Matrix IDs must pass MindRoom's Matrix user ID parser.
Strict JWT Mode
Strict mode requires each trusted-upstream request to carry both the configured identity headers and a signed JWT from the upstream gateway. Enable it when the gateway publishes a JWKS endpoint and issues short-lived assertions for authenticated browser requests. In strict mode, spoofing the trusted identity header alone is not enough because MindRoom verifies the JWT signature, expiry, issuer, audience, and configured email claim before accepting the request.
MINDROOM_TRUSTED_UPSTREAM_REQUIRE_JWT=true
MINDROOM_TRUSTED_UPSTREAM_JWT_HEADER=X-Trusted-Jwt
MINDROOM_TRUSTED_UPSTREAM_JWKS_URL=https://gateway.example.com/.well-known/jwks.json
MINDROOM_TRUSTED_UPSTREAM_JWT_AUDIENCE=mindroom-dashboard
MINDROOM_TRUSTED_UPSTREAM_JWT_ISSUER=https://gateway.example.com
MINDROOM_TRUSTED_UPSTREAM_JWT_EMAIL_CLAIM=email
MINDROOM_TRUSTED_UPSTREAM_JWT_USER_ID_CLAIM=sub
MINDROOM_TRUSTED_UPSTREAM_JWT_MATRIX_USER_ID_CLAIM=matrix_user_id
MINDROOM_TRUSTED_UPSTREAM_REQUIRE_JWT is disabled by default to preserve existing header-only deployments.
When it is set to true, MINDROOM_TRUSTED_UPSTREAM_JWT_HEADER, MINDROOM_TRUSTED_UPSTREAM_JWKS_URL, MINDROOM_TRUSTED_UPSTREAM_JWT_AUDIENCE, and MINDROOM_TRUSTED_UPSTREAM_JWT_ISSUER are required.
MINDROOM_TRUSTED_UPSTREAM_JWT_EMAIL_CLAIM defaults to email.
Set MINDROOM_TRUSTED_UPSTREAM_JWT_USER_ID_CLAIM when the trusted user ID header contains a stable ID that is distinct from the email address.
Set MINDROOM_TRUSTED_UPSTREAM_JWT_MATRIX_USER_ID_CLAIM when the trusted Matrix user ID header contains a Matrix identity that should be accepted in strict mode.
MindRoom caches the JWKS response briefly and refreshes it automatically so key rotation can take effect without fetching keys on every request.
If the JWT is missing, expired, signed by an unknown key, issued by the wrong issuer, meant for the wrong audience, missing a configured identity claim, or inconsistent with a configured trusted identity header, MindRoom returns 401.
When MINDROOM_TRUSTED_UPSTREAM_JWT_USER_ID_CLAIM is set, the trusted user ID header must match that verified JWT claim.
When MINDROOM_TRUSTED_UPSTREAM_JWT_USER_ID_CLAIM is not set, the trusted user ID header must match the verified email claim because no separate signed user ID is available.
When a trusted email header is configured, that email value must match MINDROOM_TRUSTED_UPSTREAM_JWT_EMAIL_CLAIM.
When MINDROOM_TRUSTED_UPSTREAM_JWT_MATRIX_USER_ID_CLAIM is set, MINDROOM_TRUSTED_UPSTREAM_MATRIX_USER_ID_HEADER must match that verified JWT claim.
When no Matrix user ID claim is configured, strict mode only accepts a Matrix identity derived from the verified email via MINDROOM_TRUSTED_UPSTREAM_EMAIL_TO_MATRIX_USER_ID_TEMPLATE.
That derivation can use the verified JWT email claim even when MINDROOM_TRUSTED_UPSTREAM_EMAIL_HEADER is not configured.
When no Matrix user ID claim or email-to-Matrix template is configured, strict mode rejects MINDROOM_TRUSTED_UPSTREAM_MATRIX_USER_ID_HEADER because that header is not backed by a signed identity.
Connections Portal
Set MINDROOM_CONNECTIONS_AGENT to the name of a private agent to enable /connections.
The portal lists assigned tools and groups OAuth services by agent: the selected private agent and shared agents the authenticated user can use or manage credentials for.
Services come from each authorized agent's available tools, including deferred tools and registered plugin or MCP OAuth providers.
Tools without browser authentication also appear, and room-dependent tools are marked MindRoom only.
Each service status loads independently, so a failed or unconnected service does not block the others.
The portal does not expose model configuration, generic credential editing, or OAuth client administration.
Configure strict JWT authentication as described above, including a signed Matrix user ID claim or a mapping from verified email.
The portal rejects header-only, standalone API-key, and owner-identity fallback authentication.
The selected agent must use private.per: user or private.per: user_agent:
agents:
personal:
display_name: Personal Mind
role: Personal assistant
private:
per: user_agent
access:
users: ["@*:example.org"]
tools:
- google_drive
- name: google_calendar
defer: true
Agent use requires a matching access.users grant, configured administrator authority, or verified membership in a configured grant room.
Conversation-only access.current_room_members grants do not apply because browser and MCP requests have no current Matrix room.
Shared agents also appear when the user is listed in agents.<name>.credential_managers, even when they cannot use that agent.
Users with agent access can select its compatible MCP tools and see shared connection availability without the connected account identity.
Credential managers and administrators can manage shared connections; users can manage their own requester-scoped connections.
Credential management alone does not grant MCP tool access.
The server resolves canonical Matrix aliases and rechecks agent and provider authorization for every status, connect, and disconnect request.
The browser selects an authorized agent; it cannot override the credential owner or execution scope.
Shared connections keep their configured credential scope: worker_scope: shared is per agent, while an unset scope uses the installation-wide store.
Disconnecting an account affects every agent using that credential scope.
Existing requester-only provider rules still apply.
Disconnect confirmations follow the connection's credential scope, including personal connections on shared agents.
Account linking and disconnect reuse the same OAuth state, callback, token store, and reset lifecycle used by tools.
Operators still configure OAuth clients; shared service accounts are not displayed as personal connections.
When the portal is enabled, upstream users without administrator authority cannot access administrator APIs or dashboard pages.
Existing state-bound OAuth callback, success, and reset pages remain available for account linking.
To share a hostname with another frontend, forward /connections, /connections/*, /api/connections, /api/connections/*, and the existing /api/oauth/* routes to the MindRoom API.
Keep these routes behind the authenticated upstream and exclude /connections from any other application's service-worker navigation fallback.
Portal assets are served under /connections/assets/; root /assets/ can continue serving the other application.
Use a runtime build containing the portal before enabling the routes.
Connect and disconnect requests require an HTTPS public origin and a same-origin Origin header matching MINDROOM_PUBLIC_URL, or the request base URL when unset.
The portal API returns private, non-cacheable account status and never returns token or OAuth client configuration.
The optional MCP Gateway reuses these accounts to expose selected agents' tools to external MCP clients.
When enabled, the portal lets each user choose which eligible agents are exposed through every one of their MCP clients.
Its machine endpoints use separate gateway OAuth bearer authentication; the browser consent page uses this same signed login.
Instance Chart
For the hosted instance chart, configure the equivalent values:
trustedUpstreamAuth:
enabled: "true"
userIdHeader: X-MindRoom-User-Id
emailHeader: X-MindRoom-User-Email
matrixUserIdHeader: X-MindRoom-Matrix-User-Id
emailToMatrixUserIdTemplate: "@{localpart}:example.org"
emailDomain: example.com
requireJwt: "true"
jwtHeader: X-Trusted-Jwt
jwksUrl: https://gateway.example.com/.well-known/jwks.json
jwtAudience: mindroom-dashboard
jwtIssuer: https://gateway.example.com
jwtEmailClaim: email
jwtUserIdClaim: sub
jwtMatrixUserIdClaim: matrix_user_id
The chart renders these values as the MINDROOM_TRUSTED_UPSTREAM_* runtime environment variables.
The instance chart fails rendering when trustedUpstreamAuth.emailToMatrixUserIdTemplate is set without trustedUpstreamAuth.emailHeader.
The instance chart also fails rendering when trustedUpstreamAuth.requireJwt is true without jwtHeader, jwksUrl, jwtAudience, or jwtIssuer.
Both charts require emailDomain when an email template is configured.
The template value must contain exactly one {localpart} placeholder.
When using the platform provisioner, configure the platform chart with matching provisioner values:
provisioner:
trustedUpstreamAuth:
enabled: "true"
userIdHeader: X-MindRoom-User-Id
emailHeader: X-MindRoom-User-Email
matrixUserIdHeader: X-MindRoom-Matrix-User-Id
emailToMatrixUserIdTemplate: "@{localpart}:example.org"
emailDomain: example.com
requireJwt: "true"
jwtHeader: X-Trusted-Jwt
jwksUrl: https://gateway.example.com/.well-known/jwks.json
jwtAudience: mindroom-dashboard
jwtIssuer: https://gateway.example.com
jwtEmailClaim: email
jwtUserIdClaim: sub
jwtMatrixUserIdClaim: matrix_user_id
The platform chart renders these as INSTANCE_TRUSTED_UPSTREAM_* variables on the provisioner deployment.
The platform chart fails rendering when provisioner.trustedUpstreamAuth.emailToMatrixUserIdTemplate is set without provisioner.trustedUpstreamAuth.emailHeader.
The platform chart also fails rendering when provisioner.trustedUpstreamAuth.requireJwt is true without jwtHeader, jwksUrl, jwtAudience, or jwtIssuer.
Security Boundary
Trusted upstream auth is provider-neutral.
A reverse proxy, ingress controller, OAuth2 proxy, or another gateway can provide the headers as long as MindRoom only receives gateway-verified values.
Never expose a MindRoom instance with this mode enabled directly to browsers or the public internet.
In header-only mode, every network path to MindRoom must remove any client-provided copies of the trusted headers before adding authenticated values.
In strict JWT mode, the same header-stripping requirement still applies, and MindRoom additionally validates the signed assertion.
If the configured trusted user ID header is missing, MindRoom returns 401.
If strict JWT mode is enabled and the configured JWT header is missing or invalid, MindRoom returns 401.
If a trusted browser identity does not map to the Matrix requester stored in an OAuth connect token, MindRoom returns 403.
Existing Supabase platform auth and standalone API-key auth remain available when trusted upstream auth is not enabled.
Browser mutation protection
Requests that use trusted upstream browser authentication or dashboard cookies must send an Origin matching MINDROOM_PUBLIC_URL for POST, PUT, PATCH, and DELETE operations.
If no public URL is configured, MindRoom uses the request origin.
Requests marked Sec-Fetch-Site: cross-site are rejected even when the Origin matches.
A successfully validated API bearer token does not require these browser headers.
Adding a bearer header to a request authenticated by trusted upstream identity does not bypass this protection.
The dashboard CORS allowlist controls which origins may read credentialed responses; it does not authorize cross-origin cookie or trusted-upstream mutations.
Host the dashboard under the app's public origin, or use its development proxy, which authenticates API calls with a bearer token.
Conversation-issued OAuth links for requester-scoped credentials require the intended requester to authenticate before starting authorization and again at the callback. Shared-agent credential links retain their short-lived, single-use delegation behavior.