Relay Agent
The IdentityMesh Relay Agent is a lightweight Windows service that
enables connectivity to remote identity systems without inbound
firewall rules. It runs as the IdentityMeshRelayAgent Windows
service and loads connector plug-ins from a local Connectors/
folder at startup.
Overview
A relay agent is a lightweight Windows service deployed in a remote network (branch office, partner data center, air-gapped environment) that executes import and export operations on behalf of the central sync engine. This eliminates the need to open inbound firewall ports or VPN tunnels to reach remote identity sources.
Key characteristics:
- Outbound-only connectivity — the agent connects outward to the Admin API over HTTPS. No inbound firewall rules are required in the remote network.
- Connector-agnostic — the agent loads the same connector DLLs as the sync
engine. Any
IIdentityConnectorimplementation works in relay mode. - Centrally managed — agents are registered, configured, and monitored from the Admin UI. Secrets (connection strings, credentials) are resolved centrally and transmitted over the secure channel.
Architecture
Remote Network Central Network
┌─────────────────────┐ ┌──────────────────────────────────────┐
│ │ │ │
│ Identity Source │ │ Admin API │
│ (AD, SQL, etc.) │ │ ┌──────────────────────────────┐ │
│ ▲ │ │ │ /hubs/relay (SignalR Hub) │ │
│ │ │ │ │ RelayHub │ │
│ │ LDAP/SQL │ │ └────────────▲─────────────────┘ │
│ │ │ │ │ │
│ ┌─────┴──────────┐ │ outbound │ │ │
│ │ Relay Agent │──┼──HTTPS──────────┼───────────────┘ │
│ │ (Windows Svc) │ │ SignalR │ │
│ │ │ │ │ Sync Engine ──► Projection ──► DB │
│ │ Connectors/ │ │ │ │
│ │ ├ AD.dll │ │ └──────────────────────────────────────┘
│ │ └ SQL.dll │ │
│ └────────────────┘ │
│ │
└─────────────────────┘
Data Flow
- The sync engine (or Admin API) initiates an import/export via the
POST /api/relay/importorPOST /api/relay/exportREST endpoint. - The Admin API sends the operation to the target agent through the
RelayHubSignalR hub. Configuration and auth material are included in the message. - The relay agent receives the command, loads the appropriate connector DLL, initializes it with the provided configuration, and executes the operation locally against the identity source.
- Import results are streamed back in batches of 100 objects via
ImportBatchReadyhub calls, then finalized withImportCompleted. - Connector logs are relayed back through the
RelayLoghub method and persisted to theIM_ConnectorLogstable centrally.
SignalR Transport
The relay agent uses the ASP.NET Core SignalR client with automatic reconnect. Reconnect intervals: 0s, 2s, 5s, 10s, 30s. On reconnect the agent re-registers itself with the hub.
Hub URL: /hubs/relay (gated by the RelayAuth policy — accepts the
RelayApiKey scheme via X-Agent-Id + X-Agent-ApiKey headers, or
the JwtBearer scheme with the relay.connect app role; see the
Authentication modes section under Security).
Direct Mode vs Relay Mode
Connectors default to direct mode, where the sync engine loads the connector in-process and connects to the identity source directly.
Relay mode is opt-in per connector. When a connector’s RelayAgentId is
set (via the Admin UI or the PUT /api/connectors/{id} endpoint), the sync
engine delegates that connector’s import and export operations to the
assigned relay agent instead of executing them locally.
| Aspect | Direct Mode | Relay Mode |
|---|---|---|
| Connector runs in | Sync engine process | Relay agent process |
| Network access | Engine must reach identity source | Agent must reach identity source |
| Firewall requirements | Inbound or VPN to identity source | Outbound HTTPS only from remote network |
| Configuration | ConfigJson on connector | Same ConfigJson, agent resolves locally |
| Auth material | Resolved locally by engine | Sent from central DB through SignalR |
| Logs | Written locally to DB | Relayed back through RelayLog hub method |
Deployment
Prerequisites
- Windows Server 2019 or later.
- .NET 8 Runtime.
- Outbound HTTPS connectivity from the remote network to the Admin API.
Step-by-Step
-
Register the agent in the Admin UI under Relay Agents. The UI calls
POST /api/relay-agentswith the agent name. The API returns a one-timeagentIdandapiKey— save both immediately. -
Install the relay agent on the remote server. Copy the published
IdentityMesh.Relay.Agentbinaries to a directory such asC:\Program Files\IdentityMesh\RelayAgent\. -
Copy connector DLLs into the
Connectors/subfolder under the agent’s install directory. For example:C:\Program Files\IdentityMesh\RelayAgent\ IdentityMesh.Relay.Agent.exe appsettings.json Connectors\ IdentityMesh.Connectors.ActiveDirectory.dll IdentityMesh.Connectors.Sql.dllOnly include the connector DLLs needed for identity sources in this network.
-
Configure
appsettings.json:{ "Relay": { "HubUrl": "https://identitymesh-api.contoso.com/hubs/relay", "AgentId": "a1b2c3d4-...", "AgentName": "Branch-Office-West", "ApiKey": "the-api-key-from-step-1" } }Setting Description HubUrlFull URL to the RelayHub endpoint on the Admin API AgentIdGUID returned when registering the agent AgentNameDisplay name (defaults to Environment.MachineNameif omitted)ApiKeyAPI key returned at registration (sent as X-Agent-ApiKeyheader) -
Register the Windows service:
sc create IdentityMeshRelayAgent binPath="C:\Program Files\IdentityMesh\RelayAgent\IdentityMesh.Relay.Agent.exe" -
Start the service. The agent connects to the Admin API, sends a
RegisterAgentmessage with the agent ID, machine name, version, and list of loaded connector types. The Admin API marks the agent as Online. -
Assign connectors to the agent. In the Admin UI, edit a connector and set its Relay Agent dropdown to the registered agent. This sets
RelayAgentIdon the connector record. -
Verify — the Dashboard and Relay Agents page show the agent status as Online with the loaded connector types.
Security
Authentication modes
Relay agents support two authentication modes, selected via
Relay:Authentication:Mode in appsettings.json. Both modes are
validated on the same SignalR upgrade request and gated by the same
RelayAuth policy on /hubs/relay.
| Mode | When to use | Carries credential as |
|---|---|---|
ApiKey (default) | On-prem deployments without an Entra tenant; air-gapped sites | X-Agent-Id + X-Agent-ApiKey headers |
Bearer | Cloud-native deployments; customers already using Entra for M2M auth | Authorization: Bearer <jwt> (acquired via MSAL) |
The hub’s authorization policy accepts either scheme. ApiKey-mode
agents are identified by their X-Agent-Id header (validated against
IM_RelayAgents.ApiKeyHash). Bearer-mode agents are identified by
the agent_id claim in their token (or, as a fallback, the service
principal’s object id).
API Key Authentication (default)
Each relay agent is assigned a unique API key at registration time. The API
key is SHA-256 hashed before storage in the database (ApiKeyHash column on
IM_RelayAgents). The agent sends the raw key in the X-Agent-ApiKey HTTP
header — together with X-Agent-Id — on every SignalR connection.
The Admin API validates both headers on the SignalR upgrade request: the
agent’s row is looked up by X-Agent-Id, the presented key is hashed, and
the result is compared against ApiKeyHash in constant time. A connection
without the headers, with an unknown agent id, or with a wrong key is
refused with 401. Subsequent hub method calls re-use the authenticated
identity stamped on the connection, so a caller cannot drive updates against
another agent’s row even if it knows that agent’s GUID.
The API key is returned only once at creation. If lost, delete the agent and re-register.
Bearer (Entra service principal)
Recommended for cloud-native deployments and customers using Entra for
all M2M authentication. The relay acquires an OAuth2 access token via
the MSAL ConfidentialClientApplication (client-credentials flow) and
SignalR attaches it as Authorization: Bearer ... on every connect
attempt. MSAL caches tokens in memory and auto-renews them shortly
before expiry, so there is no per-request round-trip to Entra after
the first connect.
Setup:
-
Register an Entra app for the relay (one app registration per relay, or one app for all relays if your governance allows it). If the relay runs on Azure-hosted infrastructure (VM, App Service, Container Apps), prefer a managed identity instead of a client secret.
-
Define an app role on the IdentityMesh API app registration. In the API’s app registration → App roles, add a role named
relay.connectwith Allowed member types = Applications. This role is what the API’sRelayAuthpolicy looks for on JWT-authed callers. -
Assign the
relay.connectrole to the relay’s app/identity. In the IdentityMesh API app registration → Enterprise applications → your relay app → Users and groups → add an assignment with therelay.connectrole. -
Configure the
agent_idclaim. In the relay’s app registration → Token configuration → Add optional claim, add a claim namedagent_idwhose value is the agent’sIM_RelayAgents.AgentIdGUID. Without this claim, the hub falls back to using the service principal’soidas the agent identifier, which only works if you create theIM_RelayAgentsrow with the SP’s object id as itsAgentId. -
Configure
appsettings.jsonwith the Bearer block:{ "Relay": { "HubUrl": "https://identitymesh-api.contoso.com/hubs/relay", "AgentId": "a1b2c3d4-...", "Authentication": { "Mode": "Bearer" }, "Bearer": { "TenantId": "<entra tenant guid>", "ClientId": "<relay app registration client id>", "ClientSecret": "<from secret store>", "Scope": "api://identitymesh/.default" } } }Setting Description Authentication:Mode"Bearer"to enable MSAL;"ApiKey"(default) for header authBearer:TenantIdEntra tenant GUID hosting the relay’s app registration Bearer:ClientIdApplication (client) id of the relay’s app registration Bearer:ClientSecretClient secret from the relay’s app registration. For Azure-hosted relays use a managed identity instead and omit this field. Bearer:ScopeScope to request. Typically <api-app-id-uri>/.default, e.g.api://identitymesh/.default. -
Restart the relay service. The agent will acquire its first token at connect time; rotation is handled automatically by MSAL’s in-memory cache.
In Bearer mode the legacy Relay:ApiKey value is ignored and may be
left empty.
TLS
All communication between the agent and the Admin API must use HTTPS (TLS
1.2+). The HubUrl in agent configuration should always use the https://
scheme.
Secret Handling
Connector credentials (passwords, connection strings) are stored centrally in
the IdentityMesh database. When the sync engine dispatches an operation to a
relay agent, it includes the resolved AuthMaterial in the SignalR message.
Secrets are never stored on the relay agent machine.
Heartbeat and Offline Detection
The agent sends a heartbeat every 30 seconds. The RelayAgentMonitor
background service on the Admin API checks every 30 seconds for agents whose
last heartbeat is older than 2 minutes and marks them Offline.
Agent Management API Endpoints
Relay Agents
| Method | Path | Description |
|---|---|---|
| GET | /api/relay-agents | List all registered relay agents with status |
| POST | /api/relay-agents | Register a new agent (returns agentId and one-time apiKey) |
| DELETE | /api/relay-agents/{id} | Delete agent and unassign its connectors |
Relay Operations
| Method | Path | Description |
|---|---|---|
| POST | /api/relay/import | Dispatch an import operation to a relay agent |
| POST | /api/relay/export | Dispatch an export operation to a relay agent |
| POST | /api/relay/cancel/{correlationId} | Cancel a running relay operation |
RelayHub SignalR Methods
Agent-to-Hub (Agent calls these on the hub)
| Method | Parameters | Description |
|---|---|---|
RegisterAgent | agentId, agentName, machineName, version, connectorTypes[] | Register/re-register the agent |
Heartbeat | agentId | 30-second keep-alive |
ImportBatchReady | correlationId, batch[] | Stream a batch of import results (up to 100) |
ImportCompleted | correlationId, result | Signal import success with summary |
ImportFailed | correlationId, error | Signal import failure |
ExportCompleted | correlationId | Signal export success |
ExportFailed | correlationId, error | Signal export failure |
RelayLog | correlationId, connectorId, runId, level, category, message, externalId?, elapsedMs?, detail? | Forward a connector log entry |
Hub-to-Agent (Hub pushes these to the agent)
| Method | Parameters | Description |
|---|---|---|
ExecuteImport | correlationId, connectorId, connectorType, configJson, authMaterial, importRequest | Start an import on the agent |
ExecuteExport | correlationId, connectorId, connectorType, configJson, authMaterial, changes[] | Start an export on the agent |
CancelOperation | correlationId | Cancel a running operation |
Connector Loading
The ConnectorExecutor scans the Connectors/ directory at startup for
DLLs matching the IdentityMesh.Connectors.*.dll pattern. Each DLL is
loaded into its own AssemblyLoadContext. Types implementing
IIdentityConnector are discovered via reflection and registered. This
approach avoids hard assembly references and allows the same connector DLLs
used by the sync engine to work unmodified in the relay agent.
Troubleshooting
- Agent shows Offline — check that the
HubUrlis reachable and HTTPS certificate is trusted. Review the agent’s Windows event log for connection errors. - Connector not found — verify the DLL is in the
Connectors/folder and matches theIdentityMesh.Connectors.*.dllnaming convention. - Import/export hangs — use
POST /api/relay/cancel/{correlationId}to cancel the operation. Check agent logs for connector errors.