Flow

Identity & SSO

Identity providers, JWT validation, JWKS caching, external IDs, and directory sync.

Overview

Enterprise networks can integrate with external identity providers (IDPs) to centralize authentication. Instead of relying solely on Pilot’s built-in Ed25519 keys, agents can present tokens from your organization’s IDP - OIDC, SAML, Entra ID, LDAP, or a custom webhook - and the registry validates them before granting access.

Identity integration is registry-global: a single IDP configuration applies across the registry, and setting a new one replaces the previous config. (Blueprint provisioning writes the same global store.)

Supported providers

ProviderType valueDescription
OIDCoidcOpenID Connect - standard JWT-based SSO. Works with Auth0, Okta, Google, etc.
SAMLsamlSecurity Assertion Markup Language - XML-based enterprise SSO.
Entra IDentra_idMicrosoft Entra ID (Azure AD) - native integration for Microsoft environments.
LDAPldapLightweight Directory Access Protocol - on-premises directory servers.
WebhookwebhookCustom HTTP callback - your own verification endpoint for non-standard systems.

Configuration

Configure an identity provider with the set_idp_config protocol command:

# OIDC example
{
  "type": "set_idp_config",
  "idp_type": "oidc",
  "url": "https://accounts.example.com/.well-known/openid-configuration",
  "client_id": "pilot-network-prod",
  "admin_token": "your-admin-token"
}

The IDP configuration is stored in the registry and persists across restarts. You can also set the IDP through a blueprint using the identity_provider field.

To query the current configuration:

{
  "type": "get_idp_config",
  "admin_token": "your-admin-token"
}

An audit event (idp.configured) is emitted when the IDP is set or changed.

JWT validation

The registry includes built-in JWT validation supporting two algorithms:

AlgorithmKey typeUse case
RS256RSA public key (from JWKS)Standard OIDC/OAuth2 providers
HS256Shared secretSimple integrations, internal services

Validate a token with the validate_token protocol command:

{
  "type": "validate_token",
  "token": "eyJhbGciOiJSUzI1NiIs...",
  "admin_token": "your-admin-token"
}

Returns: verified (bool), subject (the token subject), issuer, and — on failure — error (string describing why validation failed).

Validated claims

The validator checks:

JWKS caching

For RS256 tokens, the registry fetches the provider’s JSON Web Key Set (JWKS) to obtain public keys for signature verification. JWKS responses are cached to avoid hitting the provider on every validation.

ParameterValue
Cache TTL5 minutes
Max response size64 KB
Key matchingBy kid (Key ID) header in the JWT
RefreshOn TTL expiry, URL change, or empty cache. A kid missing from a fresh (non-expired) cache returns an error rather than refetching.

If the JWKS endpoint is unreachable and the cache has expired, validation fails with a clear error. The registry does not fall back to accepting unverified tokens.

Security

Algorithm confusion prevention

The validator blocks the classic algorithm-confusion attack by cross-checking the JWT's alg header against the matched JWKS key's own type (RSA for RS256, oct for HS256) — an HS256 token cannot be verified against an RSA signing key. There is no separately-configured algorithm; RS256 and HS256 are the two supported algorithms.

Clock skew tolerance

A 60-second clock skew tolerance is applied to the time-based claims that are checked (exp and nbf). This accommodates minor clock differences between the IDP and the registry without opening a significant window for expired tokens.

Webhook identity

For systems that don’t support OIDC or SAML, configure a webhook identity provider. The registry sends the agent’s credentials to your HTTP endpoint, and your endpoint returns an approval or rejection.

# Configure a webhook IDP
{
  "type": "set_idp_config",
  "idp_type": "webhook",
  "url": "https://auth.example.com/verify-agent",
  "admin_token": "your-admin-token"
}

Your endpoint receives a POST with the agent’s identity information and must return a JSON response indicating whether the agent is authorized.

External IDs

Map agents to external identity systems using external IDs:

# Set an external ID for an agent
{
  "type": "set_external_id",
  "node_id": 5,
  "external_id": "user@example.com",
  "admin_token": "your-admin-token"
}

# Look up an agent’s identity
{
  "type": "get_identity",
  "node_id": 5,
  "admin_token": "your-admin-token"
}

External IDs are free-form strings - email addresses, UPNs, LDAP DNs, or any identifier from your external system. They are stored in the registry and included in audit events. An identity.external_id_set audit event is emitted on change, recording both old and new values.

Directory sync

Directory sync pushes entries from an external directory (AD, Entra ID, LDAP) to the registry. It updates roles for existing members, removes members no longer in the directory, and stores role pre-assignments for identities that have not joined yet — it does not itself add (join) new members.

Sync operation

{
  "type": "directory_sync",
  "network_id": 1,
  "entries": [
    {
      "external_id": "alice@example.com",
      "display_name": "Alice",
      "role": "admin"
    },
    {
      "external_id": "bob@example.com",
      "display_name": "Bob",
      "role": "member"
    }
  ],
  "remove_unlisted": true,
  "admin_token": "your-admin-token"
}

What sync does

  1. Matches entries - each entry is matched to an existing member by normalized external_id
  2. Pre-assigns unmatched - entries with no matching member are counted as unmapped and stored as role pre-assignments applied when that identity later joins
  3. Maps roles - the role field sets the RBAC role; owner, admin, and member are all assignable.
  4. Reads external IDs - the external_id field is used to match; the node must already carry an external ID (set via set_external_id or at register) to be matched
  5. Removes unlisted - if remove_unlisted is true, members not in the entries list are kicked from the network

RBAC pre-assignments

Directory sync supports role pre-assignment: an identity not yet in the network gets its assigned role stored, so when the matching node later joins it receives that role instead of defaulting to member. This is useful for provisioning admin roles in bulk.

Query sync status

{
  "type": "directory_status",
  "network_id": 1,
  "admin_token": "your-admin-token"
}

Returns network_id, total, mapped, unmapped, pre_assignments, enterprise, and last_sync.

A directory.synced audit event is emitted after each sync, recording the network ID and the mapped, updated, disabled, and unmapped counts.

See also: RBAC & Access Control - role definitions and the permissions matrix. Blueprints - include IDP configuration in declarative provisioning.