|
FreeRDP
|
This document describes the JSON-RPC protocol spoken between FreeRDP and an AAD auth helper subprocess, over a dedicated pair of pipes (not stdin/stdout — see Transport). The protocol is helper-implementation-agnostic — any binary that speaks it can be pointed to via /azure:auth-helper:<path> (see client/SDL/common/sdl_aad_helper.cpp). Two implementations exist today:
| Implementation | Directory | CMake option | Binary |
|---|---|---|---|
| Native OS webview (GTK/Cocoa/WebView2) | client/common/webview-aad-helper/ | WITH_WEBVIEW_AAD_AUTH_HELPER | freerdp-webview-aad-helper |
Qt (QWebEngineView) | client/common/qt-aad-helper/ | WITH_QT_AAD_AUTH_HELPER | freerdp-qt-aad-helper |
client/common/aad_helper.c / include/freerdp/client/aad_helper.hctest as TestQtAadAuthHelperProtocol (registered only when WITH_QT_AAD_AUTH_HELPER=ON and a Python3 interpreter is found): client/common/qt-aad-helper/test/test_qt_aad_auth_helper.py. Spawns the real freerdp-qt-aad-helper binary under QT_QPA_PLATFORM=offscreen against a local HTTP redirect fixture, covering both a plain https-shaped redirect_uri and a real HTTP redirect into AAD's ms-appx-web:// native-broker scheme (see kBrokerScheme's comment in that helper's main.cpp for the two Chromium/QtWebEngine issues this guards against). Skips (rather than fails) if the helper doesn't answer the initial hello handshake at all, since that means the environment can't run Qt WebEngine rather than that the helper is broken.A helper has no OAuth-specific knowledge. It is handed a URL to display in a browser widget and a redirect_uri prefix to watch for, and it reports back whatever URI the browser eventually navigated to (or an error). All OAuth semantics — building the authorize URL, extracting the code, exchanging it for a token — stay client-side in client/common/client.c.
The JSON-RPC channel travels over two dedicated anonymous pipes. FreeRDP creates them and passes the helper's end of each as a command line argument (encoded via winpr_exportHandleToString(), decoded via winpr_importHandleFromString() — see winpr/include/winpr/handle.h for the encoding, which is an internal detail of that pair of functions, not part of this protocol):
--cmdInFd=<value> — the helper reads requests/notifications from this one.--cmdOutFd=<value> — the helper writes responses/notifications to this one.A helper parses both switches at startup and fails immediately if either is missing.
\n.Every request and response carries "jsonrpc": "2.0". Requests carry an integer id allocated by FreeRDP (monotonically increasing, starting at 1); notifications omit id.
Sent once immediately after spawning the helper, before anything else. Used as a handshake to confirm the helper started correctly and speaks a compatible protocol version.
Response:
aad_auth_helper_start() treats any response containing a result object as success; the protocol_version/helper fields are informational only today (not version-checked). helper identifies the implementation, e.g. freerdp-qt-aad-helper/1.0 for the Qt-based one.
Tells the helper to point its webview at url and wait until the browser navigates to a URI starting with redirect_uri, or until timeout_ms elapses. Only one navigate may be in flight at a time per helper instance.
title — window title for the popup (may be empty).url — initial URL to load (typically an AAD /authorize URL).redirect_uri — prefix match against the URL-decoded navigation target; matching is case-insensitive (see RedirectWatcher::matches).timeout_ms — 0 means "use the helper's default" (180000 ms).Success response — result.redirect_url is the full, verbatim URI the browser navigated to (FreeRDP extracts the code/error query parameters from it):
Failure response:
error.message is one of:
| message | meaning |
|---|---|
navigate_already_in_progress | a previous navigate on this helper hasn't completed yet |
timeout | timeout_ms elapsed with no matching navigation |
user_cancelled | the popup was closed by the user, or a cancel notification was received |
shutting_down | the helper is exiting (see shutdown/exit) |
<idp error>[: <idp error_subcode>] | the IdP redirected to redirect_uri with an error query parameter (e.g. access_denied); error_subcode, if present, is appended after : |
Requests a clean shutdown acknowledgment before the helper process is torn down. Always answered with a null result (never an error):
Tells the helper to stop reading requests and terminate its webview/event loop. Sent right after shutdown completes. aad_auth_helper_stop() then waits up to 3 seconds for the process to exit before forcibly terminating it.
Cancels the in-flight navigate request, if any; the pending navigate response resolves with error.message = "user_cancelled". No-op if nothing is in flight. Not currently sent by the FreeRDP-side transport shim, but implemented and tested for future use (e.g. a user-initiated cancel button).
Forwarded to WLog_INFO. Recognized by the client-side transport but not currently emitted by the helper implementation; reserved for future diagnostic use.
hello; failure to get a valid result tears the process down immediately.navigate requests over the life of one RDP connection. The helper's instance (and its cookies/session) is reused across all of them, so e.g. an AVD gateway auth followed by a target-host auth only prompts the user once.shutdown, waits for its response, then sends exit and waits up to 3 seconds for the process to exit before calling TerminateProcess.id that doesn't match the currently awaited request are dropped with a WLog_WARN (defensive against stray or out-of-order lines; in practice the helper answers strictly in request order).id is transported as a JSON number on the wire; FreeRDP treats it as a UINT32.