Configure agent authentication
Add one or more validators under spec.externalAuth so that your applications can call an agent. For how the validator chain works, see Authentication architecture. For every field, see AgentRuntime: externalAuth. For dashboard sign-in, see Configure dashboard authentication.
With no spec.externalAuth, the external listener returns 401 to every request. Only the dashboard’s “Try this agent” view reaches the agent, over the internal management twin.
Prerequisites
Section titled “Prerequisites”- An AgentRuntime with at least one facade, for example
my-agentin namespacemy-namespace. - For client keys: a checkout of the Omnia repository (for
hack/mint-client-key.sh),kubectlandopenssl. - For OIDC: an identity provider whose discovery document the operator can reach.
- For edge-trust: an edge that verifies the JWT and injects claim headers.
Choose validators
Section titled “Choose validators”Use clientKeys for a few known applications, oidc for end users who hold tokens from your identity provider, and edgeTrust when a gateway or mesh already verifies the JWT. You can enable all three. The facade tries them in that order and admits the first that accepts the request. A validator that recognises a credential and rejects it, such as an expired JWT, ends the chain.
Add client keys
Section titled “Add client keys”-
Enable the validator. An empty object is enough:
spec:externalAuth:clientKeys: {}Apply it with
kubectl apply -f. AdddefaultRoleortrustEndUserHeaderunderclientKeysif you need them. -
Mint a key:
Terminal window hack/mint-client-key.sh \--context my-cluster \--namespace my-namespace \--agent my-agent \--id terraform \--role premium \--expires-at 2027-01-01T00:00:00ZThe script requires
--context. Add--claim key=valuefor further claims. It prints the raw token to stdout once. The cluster stores only the SHA-256 hash, so capture the token or mint another key. -
Give the token to the caller. The caller sends
Authorization: Bearer <token>. -
To revoke a key, delete its Secret. No AgentRuntime change is needed:
Terminal window kubectl delete secret agent-my-agent-clientkey-terraform -n my-namespace
The key’s claims reach ToolPolicy as principal.claims. A key minted with --role premium is matched by principal.claims has role && principal.claims.role == "premium". A key with no claims gets role set to defaultRole. See Configure tool policies.
To create the Secret without the script, follow the client key Secret contract. The facade skips a Secret whose ownerReferences do not name the AgentRuntime’s UID, and one whose keyHash is not 32 raw bytes. Each skip is logged.
Add OIDC
Section titled “Add OIDC”-
Point the facade at your issuer:
spec:externalAuth:oidc:issuer: "https://auth.example.com"audience: "my-agent"issuermust equal theissclaim in your tokens exactly. These are common issuer shapes. Confirm yours in the provider’s discovery document.Provider issuerAuth0 https://<tenant>.auth0.com/Okta https://<org>.okta.com/oauth2/defaultGoogle https://accounts.google.comKeycloak https://<host>/realms/<realm>Azure AD https://login.microsoftonline.com/<tenant-id>/v2.0 -
Wait for the controller to mirror the JWKS, then check the condition:
Terminal window kubectl get agentruntime my-agent -n my-namespace \-o jsonpath='{.status.conditions[?(@.type=="OIDCJWKSReady")]}'The controller creates the Secret
agent-my-agent-oidc-jwksand refreshes it every six hours. The facade verifies tokens in-process, so no service mesh is needed.
The IdP’s role and group claims reach ToolPolicy under their own names, for example principal.claims.groups. claimMapping renames only the subject and end-user claims.
A WebSocket connection admitted by an OIDC token closes with code 4401 shortly before the token’s exp. To keep it open, send a new token in a credential_refresh frame before then. See WebSocket protocol.
Bind tokens to one conversation
Section titled “Bind tokens to one conversation”If your issuer mints a token per conversation, name the claim that identifies the conversation:
spec: externalAuth: oidc: issuer: "https://auth.example.com" audience: "my-agent" sessionClaim: diagnostic_session_idThe facade then refuses a token without the claim, and a token can resume only a session that was started with the same claim value. This applies to WebSocket sessions and A2A contexts. An A2A client starts a conversation by sending no contextId, because a contextId the facade has not recorded is refused. The check needs session-api. Without it, every resume is refused. A session that session-api could not record, which the facade then runs unrecorded, cannot be resumed by a bound client either.
Add edge-trust
Section titled “Add edge-trust”-
Name the headers your edge injects. The defaults match the chart’s Istio layout:
spec:externalAuth:edgeTrust:headerMapping:subject: x-user-idendUser: x-user-idemail: x-user-emailclaimsFromHeaders:x-user-groups: groupsA request is admitted when the subject header is present. The role header
x-user-rolesis always read into theroleclaim. To expose other headers, list them inclaimsFromHeaders. -
Make the edge strip inbound copies of every header in
headerMappingandclaimsFromHeaders.
Keep the dashboard debug view
Section titled “Keep the dashboard debug view”managementPlane is set per facade and defaults to true. Set it to false on a facade to remove its management twin, which blocks the dashboard debug view for that facade:
spec: facades: - type: websocket managementPlane: falseConnect with a token
Section titled “Connect with a token”The facade reads the credential only from the Authorization header of the WebSocket upgrade request. A browser’s WebSocket constructor cannot set that header, so browser callers connect through a server-side proxy.
websocat -H "Authorization: Bearer $TOKEN" \ "wss://<agent-host>/ws?agent=my-agent"import WebSocket from 'ws';
const ws = new WebSocket('wss://<agent-host>/ws?agent=my-agent', { headers: { Authorization: `Bearer ${token}` },});See Expose agents for the host.
Verify
Section titled “Verify”-
Confirm the
ExternalAuthcondition lists your validators:Terminal window kubectl get agentruntime my-agent -n my-namespace \-o jsonpath='{.status.conditions[?(@.type=="ExternalAuth")].message}'The message has the form
facade admits: clientKeys, managementPlane. -
Connect without a token. The upgrade is refused with 401.
-
Connect with a valid token. The facade sends a
connectedmessage.
Troubleshooting
Section titled “Troubleshooting”Every request returns 401
Section titled “Every request returns 401”The facade logs the first rejection of each cause at the default level and repeats at debug level (LOG_LEVEL=debug, set through spec.facades[].extraEnv):
kubectl logs -n my-namespace \ -l app.kubernetes.io/name=omnia-agent,app.kubernetes.io/instance=my-agent \ -c facade --tail=50Look for auth middleware rejected request. The reasonCode field names the cause, and remedy says what to change:
reasonCode | Cause |
|---|---|
no_credential | No Authorization header, or no validator recognised it. Also appears when a management token is sent to the external port |
expired | The key or JWT has expired |
bad_signature, unknown_key, bad_algorithm, malformed_token | The JWT does not verify against the JWKS. oidc accepts RS256 only |
bad_issuer, bad_audience | The token’s iss or aud differs from issuer or audience |
missing_session_claim | The token lacks the claim that oidc.sessionClaim names |
OIDC tokens are rejected
Section titled “OIDC tokens are rejected”kubectl get secret agent-my-agent-oidc-jwks -n my-namespacekubectl describe agentruntime my-agent -n my-namespaceIf the Secret is missing, the OIDCJWKSReady condition gives the reason, for example DiscoveryFailed. Check that the operator pod can reach the issuer. The facade does not start with oidc enabled until the Secret exists. If tokens are still rejected, compare issuer with the iss claim character by character, including any trailing slash.
Edge-trust claims are missing downstream
Section titled “Edge-trust claims are missing downstream”Tool handlers receive edge-trust claims as X-Omnia-Claim-<Name> headers, and ToolPolicy reads them as principal.claims. If they are missing:
- Confirm the edge injects the expected headers, using a debug container or Envoy access logs.
- Confirm the edge strips inbound copies of those headers.
- Confirm
claimsFromHeaderslists the header names as the edge emits them. Header names are case-insensitive.
Related pages
Section titled “Related pages”- Authentication architecture explains the validator chain and the trust boundaries.
- AgentRuntime CRD lists the
externalAuthfields and the client key Secret. - Configure tool policies uses the admitted claims in ToolPolicy rules.
- Expose agents routes external traffic to the facade.