You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
In enterprise environments such as Microsoft Fabric Notebooks, every network request from the client must route through a corporate proxy before reaching Azure Fluid Relay (AFR). The reasons are:
Access Control
Fabric needs real-time control over client permissions. When clients connect directly to AFR, Fabric can only authenticate at token issuance time — once issued, the token cannot be revoked until expiry. Routing traffic through Proxy allows Fabric to enforce permission checks on every request, immediately cut off access when a user's permissions change (e.g., removed from a shared notebook, tenant policy updates), and maintain audit logs for compliance.
VNet Customer Support
For enterprise customers accessing Fabric through Azure Virtual Network (VNet), direct connectivity to AFR's public endpoints is physically impossible — outbound traffic to public internet is blocked by network isolation rules (NSG, no public routing). The Fabric Proxy, exposed via Private Endpoint within the customer's VNet, acts as a network bridge: the client connects to the Proxy over the private network, and the Proxy forwards traffic to AFR on the client's behalf. Without the Proxy, VNet customers simply cannot use collaboration features at all.
This proxy layer is responsible for:
Identity validation and control: The proxy authenticates the client using an internal business token (e.g., MWC Token in the Fabric scenario) before forwarding to AFR. The proxy only cares about the MWC Token — the AFR JWT, which is embedded inside the request body (via the restless/form-tunneling mechanism), is passed through transparently without inspection or modification. It also controls the permisssions of clients according to business requirements.
Dynamic URL routing: When the client first calls the AFR session discovery endpoint, the response contains three dynamically assigned regional service URLs (ordererUrl, historianUrl, deltaStreamUrl). The proxy intercepts this response, records the mapping from documentId to these URLs, and rewrites them to point back to the proxy itself. All subsequent requests from the client are then routed by the proxy to the correct AFR regional endpoint based on this cached mapping.
Network enforcement: All traffic must go through the proxy — direct connections to *.fluidrelay.azure.com are blocked by network policy.
Currently, the AzureClient does not support:
Setting a custom proxy URL to redirect all AFR HTTP requests (the endpoint field routes only to the AFR session discovery endpoint, but subsequent requests to dynamically discovered ordererUrl, historianUrl, and deltaStreamUrl bypass the proxy entirely)
Injecting a custom Authorization header into HTTP requests (the restless/form-tunneling mechanism encodes all headers into the POST body, making them invisible to an intermediate HTTP proxy)
Setting a Sec-WebSocket-Protocol header in the WebSocket upgrade request (the only browser-compatible mechanism for carrying a custom authentication token in the WebSocket handshake, since browsers prohibit custom WebSocket headers)
This limitation makes it impossible to deploy Fluid Framework in environments that require a mandated proxy layer, such as Microsoft Fabric.
Related prior issue: #12666 (closed as stale, never implemented)
Describe the solution you'd like
We propose adding Proxy Mode support through three complementary mechanisms:
1. Uniform Proxy URL
Add a proxyUrl option to IRouerliciousDriverPolicies (and correspondingly to AzureClientProps). When set, all outgoing requests — including dynamically discovered ordererUrl, historianUrl, deltaStreamUrl — are routed through this proxy URL rather than directly to AFR endpoints.
// Proposed API addition to IRouerliciousDriverPoliciesinterfaceIRouerliciousDriverPolicies{// ... existing fields .../** * When set, all HTTP and WebSocket requests to AFR services are routed * through this proxy URL instead of connecting directly to AFR endpoints. * The proxy is responsible for forwarding to the appropriate AFR service. * Overrides dynamically discovered ordererUrl, historianUrl, and deltaStreamUrl. */proxyUrl?: string;}
Usage example:
constclientProps: AzureClientProps={connection: {type: "remote",tenantId: "...",tokenProvider: myTokenProvider,endpoint: "https://my-proxy.example.com",// proxy handles discovery},driverPolicies: {proxyUrl: "https://my-proxy.example.com",// all subsequent requests also go here}};
Add an additionalAuthHeaderProvider callback to IRouerliciousDriverPolicies. When provided, the returned headers are included in the actual HTTP request headers (not inside the restless body) so that an intermediate proxy can inspect them for authentication.
Important: the AFR JWT token remains inside the restless-encoded request body and is passed through by the proxy transparently. This additional header is solely for the proxy to verify the caller's business identity (MWC Token) before forwarding — the proxy does not modify the body payload.
interfaceIRouerliciousDriverPolicies{// ... existing fields .../** * Optional async callback that provides additional HTTP headers to include * in every outbound request. These headers are sent as real HTTP headers * (not encoded into the restless body), making them inspectable by proxies. * * The proxy uses these headers solely for caller identity validation * (e.g. verifying a MWC Token). The AFR JWT remains in the request body * (restless-encoded) and is forwarded by the proxy without modification. */additionalAuthHeaderProvider?: ()=>Promise<Record<string,string>>;}
3. Custom Sec-WebSocket-Protocol for WebSocket Upgrade (for WebSocket connections)
Add a webSocketSubprotocolProvider callback that allows injecting a custom WebSocket subprotocol string. This is the only browser-compatible mechanism for passing authentication data in the WebSocket HTTP upgrade request, since browsers prohibit setting arbitrary WebSocket headers.
The pattern (encoding a token as a subprotocol prefix) is already established by Azure services — for example, Azure Relay and Microsoft Fabric itself use access-token#<token> as the subprotocol value.
Important: the proxy reads this subprotocol header only for MWC Token validation at the HTTP upgrade phase. The AFR JWT (carried later in the Socket.IO connect_document event payload within the WebSocket stream) is passed through transparently — the proxy does not inspect or modify Socket.IO frame content.
interfaceIRouerliciousDriverPolicies{// ... existing fields .../** * Optional async callback that provides a WebSocket subprotocol string * to include in the Sec-WebSocket-Protocol header of the WebSocket upgrade * request. This is the only browser-compatible way to pass authentication * data in the WebSocket handshake (browsers prohibit arbitrary WS headers). * * Common pattern: "access-token#<business-token>" * * The proxy reads this from req.headers['sec-websocket-protocol'] during * the HTTP upgrade phase to validate the caller's MWC Token, then forwards * the WebSocket connection to the appropriate AFR regional endpoint * (looked up from the cached session discovery mapping). The proxy does * NOT inspect or modify any Socket.IO frames inside the WebSocket stream. */webSocketSubprotocolProvider?: ()=>Promise<string>;}
This maps to the protocols parameter in Socket.IO, which becomes Sec-WebSocket-Protocol in the HTTP upgrade request:
Azure Relay / Power BI Embedded / Microsoft Fabric: Already use Sec-WebSocket-Protocol: access-token#<token> to carry bearer tokens in WebSocket upgrades. This is an established pattern across Azure services (visible in browser DevTools → Network → Socket → Headers → Sec-WebSocket-Protocol).
Issue Feature: support custom headers in RouterliciousDocumentServiceFactory #12666 (Oct 2022, closed stale): Requested customHeaderProvider for RouterliciousDocumentServiceFactory. Discussed adding to driverPolicies but never implemented. Key finding from that discussion: with enableRestless: true (current default), all headers (including AFR JWT) are encoded into the request body and are invisible to proxies as real HTTP headers — which is exactly why a separate additionalAuthHeaderProvider for the business token is needed, while the AFR JWT continues to travel inside the body as-is.
Socket.IO protocols option: Socket.IO client already supports passing subprotocols via the protocols constructor option, which the browser maps to Sec-WebSocket-Protocol. No new browser API is needed — this is purely an SDK-level change to expose the existing option.
Describe alternatives you've considered
Cookie-based token passing: Cookies are automatically carried in WebSocket upgrade requests and all HTTP requests within the same origin. However, they require the proxy to serve a login endpoint to set HttpOnly cookies and don't work cross-origin without careful SameSite/Secure configuration. More complex to manage than header-based approaches.
Query parameter token: Encoding the token in the WebSocket URL (?token=...). Works in browsers, but tokens in URLs appear in server logs, browser history, and referrer headers — significant security risk for long-lived tokens.
Patching the compiled SDK in node_modules: Possible to prototype by patching documentDeltaConnection.js directly, but not a production solution as it breaks on every npm install.
Socket.IO auth field: Socket.IO supports an auth option that is sent in the Socket.IO CONNECT packet payload (wire format: 40{"token":"..."}) after the WebSocket upgrade completes. This works, but requires the proxy to parse Socket.IO protocol frames rather than reading a plain HTTP header — significantly more complex proxy implementation. Additionally, Fluid SDK currently sends the AFR JWT in the connect_document event payload (a further Socket.IO event after CONNECT), not in auth.
Forking the SDK: Maintaining a private fork of @fluidframework/routerlicious-driver and @fluidframework/azure-client. Viable but creates a long-term maintenance burden tracking upstream changes.
Proxy validates MWC Token only: The business identity header (Authorization: MwcToken ... for HTTP, Sec-WebSocket-Protocol: access-token#... for WebSocket) is read by the proxy for authentication. The proxy does not generate or replace the AFR JWT.
Payload is fully transparent: The restless-encoded request body (which contains the AFR JWT) is forwarded to AFR byte-for-byte without modification.
URL routing via session cache: The proxy intercepts the session discovery response, caches the documentId → {ordererUrl, historianUrl, deltaStreamUrl} mapping, rewrites the URLs to point to itself, and uses the cache to route all subsequent requests to the correct AFR regional endpoint.
WebSocket is a transparent tunnel: After MWC Token validation at the HTTP upgrade phase, the proxy establishes a WebSocket tunnel to the cached deltaStreamUrl. All Socket.IO frames (including connect_document with AFR JWT) flow through without inspection.
The three proposed SDK changes together (proxy URL + HTTP auth header + WS subprotocol) cover all AFR communication channels and enable a complete proxy architecture without distributing any AFR private keys to the client.
Feature
Is your feature request related to a problem? Please describe.
In enterprise environments such as Microsoft Fabric Notebooks, every network request from the client must route through a corporate proxy before reaching Azure Fluid Relay (AFR). The reasons are:
Fabric needs real-time control over client permissions. When clients connect directly to AFR, Fabric can only authenticate at token issuance time — once issued, the token cannot be revoked until expiry. Routing traffic through Proxy allows Fabric to enforce permission checks on every request, immediately cut off access when a user's permissions change (e.g., removed from a shared notebook, tenant policy updates), and maintain audit logs for compliance.
For enterprise customers accessing Fabric through Azure Virtual Network (VNet), direct connectivity to AFR's public endpoints is physically impossible — outbound traffic to public internet is blocked by network isolation rules (NSG, no public routing). The Fabric Proxy, exposed via Private Endpoint within the customer's VNet, acts as a network bridge: the client connects to the Proxy over the private network, and the Proxy forwards traffic to AFR on the client's behalf. Without the Proxy, VNet customers simply cannot use collaboration features at all.
This proxy layer is responsible for:
ordererUrl,historianUrl,deltaStreamUrl). The proxy intercepts this response, records the mapping fromdocumentIdto these URLs, and rewrites them to point back to the proxy itself. All subsequent requests from the client are then routed by the proxy to the correct AFR regional endpoint based on this cached mapping.*.fluidrelay.azure.comare blocked by network policy.Currently, the
AzureClientdoes not support:endpointfield routes only to the AFR session discovery endpoint, but subsequent requests to dynamically discoveredordererUrl,historianUrl, anddeltaStreamUrlbypass the proxy entirely)Authorizationheader into HTTP requests (the restless/form-tunneling mechanism encodes all headers into the POST body, making them invisible to an intermediate HTTP proxy)Sec-WebSocket-Protocolheader in the WebSocket upgrade request (the only browser-compatible mechanism for carrying a custom authentication token in the WebSocket handshake, since browsers prohibit custom WebSocket headers)This limitation makes it impossible to deploy Fluid Framework in environments that require a mandated proxy layer, such as Microsoft Fabric.
Related prior issue: #12666 (closed as stale, never implemented)
Describe the solution you'd like
We propose adding Proxy Mode support through three complementary mechanisms:
1. Uniform Proxy URL
Add a
proxyUrloption toIRouerliciousDriverPolicies(and correspondingly toAzureClientProps). When set, all outgoing requests — including dynamically discoveredordererUrl,historianUrl,deltaStreamUrl— are routed through this proxy URL rather than directly to AFR endpoints.Usage example:
2. Custom HTTP Authorization Header (for HTTP requests)
Add an
additionalAuthHeaderProvidercallback toIRouerliciousDriverPolicies. When provided, the returned headers are included in the actual HTTP request headers (not inside the restless body) so that an intermediate proxy can inspect them for authentication.Important: the AFR JWT token remains inside the restless-encoded request body and is passed through by the proxy transparently. This additional header is solely for the proxy to verify the caller's business identity (MWC Token) before forwarding — the proxy does not modify the body payload.
Usage example:
3. Custom
Sec-WebSocket-Protocolfor WebSocket Upgrade (for WebSocket connections)Add a
webSocketSubprotocolProvidercallback that allows injecting a custom WebSocket subprotocol string. This is the only browser-compatible mechanism for passing authentication data in the WebSocket HTTP upgrade request, since browsers prohibit setting arbitrary WebSocket headers.The pattern (encoding a token as a subprotocol prefix) is already established by Azure services — for example, Azure Relay and Microsoft Fabric itself use
access-token#<token>as the subprotocol value.Important: the proxy reads this subprotocol header only for MWC Token validation at the HTTP upgrade phase. The AFR JWT (carried later in the Socket.IO
connect_documentevent payload within the WebSocket stream) is passed through transparently — the proxy does not inspect or modify Socket.IO frame content.This maps to the
protocolsparameter in Socket.IO, which becomesSec-WebSocket-Protocolin the HTTP upgrade request:Existing work
Sec-WebSocket-Protocol: access-token#<token>to carry bearer tokens in WebSocket upgrades. This is an established pattern across Azure services (visible in browser DevTools → Network → Socket → Headers →Sec-WebSocket-Protocol).customHeaderProviderforRouterliciousDocumentServiceFactory. Discussed adding todriverPoliciesbut never implemented. Key finding from that discussion: withenableRestless: true(current default), all headers (including AFR JWT) are encoded into the request body and are invisible to proxies as real HTTP headers — which is exactly why a separateadditionalAuthHeaderProviderfor the business token is needed, while the AFR JWT continues to travel inside the body as-is.protocolsoption: Socket.IO client already supports passing subprotocols via theprotocolsconstructor option, which the browser maps toSec-WebSocket-Protocol. No new browser API is needed — this is purely an SDK-level change to expose the existing option.Describe alternatives you've considered
Cookie-based token passing: Cookies are automatically carried in WebSocket upgrade requests and all HTTP requests within the same origin. However, they require the proxy to serve a login endpoint to set
HttpOnlycookies and don't work cross-origin without carefulSameSite/Secureconfiguration. More complex to manage than header-based approaches.Query parameter token: Encoding the token in the WebSocket URL (
?token=...). Works in browsers, but tokens in URLs appear in server logs, browser history, and referrer headers — significant security risk for long-lived tokens.Patching the compiled SDK in
node_modules: Possible to prototype by patchingdocumentDeltaConnection.jsdirectly, but not a production solution as it breaks on everynpm install.Socket.IO
authfield: Socket.IO supports anauthoption that is sent in the Socket.IOCONNECTpacket payload (wire format:40{"token":"..."}) after the WebSocket upgrade completes. This works, but requires the proxy to parse Socket.IO protocol frames rather than reading a plain HTTP header — significantly more complex proxy implementation. Additionally, Fluid SDK currently sends the AFR JWT in theconnect_documentevent payload (a further Socket.IO event after CONNECT), not inauth.Forking the SDK: Maintaining a private fork of
@fluidframework/routerlicious-driverand@fluidframework/azure-client. Viable but creates a long-term maintenance burden tracking upstream changes.Additional context
The full request flow in proxy mode would be:
Key design principles:
Authorization: MwcToken ...for HTTP,Sec-WebSocket-Protocol: access-token#...for WebSocket) is read by the proxy for authentication. The proxy does not generate or replace the AFR JWT.documentId → {ordererUrl, historianUrl, deltaStreamUrl}mapping, rewrites the URLs to point to itself, and uses the cache to route all subsequent requests to the correct AFR regional endpoint.deltaStreamUrl. All Socket.IO frames (includingconnect_documentwith AFR JWT) flow through without inspection.The three proposed SDK changes together (proxy URL + HTTP auth header + WS subprotocol) cover all AFR communication channels and enable a complete proxy architecture without distributing any AFR private keys to the client.