Security & Auth

Protect AgentOS APIs with JWT verification, scoped permissions, request isolation, and per-user data boundaries.

Teams serving agents to employees or customers need identity, permissions, and user data boundaries at the runtime. AgentOS verifies JWTs, enforces scopes per endpoint, creates a fresh component copy for core run endpoints, and can scope persistent user data to the JWT subject. Tokens can come from the Control Plane, your backend, or an external identity provider.

BoundaryControl
Caller identityJWT signature verification; set verify_audience=True to enforce the aud claim
API accessScope checks on mapped endpoints and built-in resource handlers; add mappings for custom REST routes
Run stateFresh component copy for core run endpoints, with some resources shared by reference
Persistent user dataOpt-in reads, writes, and ownership checks scoped to the JWT subject
Network and databaseReverse proxy controls and database permissions configured by the deployment

Network-layer controls (rate limiting, WAF, IP allowlists, mTLS) live at your reverse proxy or API gateway layer.

Authentication

A production AgentOS sits behind JWT-validating middleware. Tokens can come from the AgentOS control plane, your own backend, or a third-party identity provider. Your service verifies them with the matching public key. See Self-Hosted for BYO and third-party setup.

from agno.os import AgentOS

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,  # reads JWT_VERIFICATION_KEY from env
)

authorization=True drives both layers:

  • Authentication (this section): verifies JWTs on protected central routes. Configured service-account tokens are also accepted and checked through their own verifier.
  • Authorization (below): enforces the token's scopes per endpoint.

Discovery and documentation routes are exempt from the JWT requirement: /, /health, /info, /docs, /redoc, /openapi.json, /docs/oauth2-redirect. Slack, Telegram, and WhatsApp routes use their interface-specific request verification instead of central JWT middleware. The MCP Server Card is also public when enabled, while MCP OAuth can delegate authentication to its configured provider. See Surface-specific policies.

Generate a Verification Key from the Control Plane

Toggle JWT authorization

Open os.agno.comConnect OSLive → paste your URL. Enable JWT authorization when connecting a new AgentOS, or later from the OS Settings page.

Copy the public key

Copy the public key for your AgentOS from the modal.

Set the verification key

Set the JWT_VERIFICATION_KEY environment variable to your public key in your .env file or export it directly in your terminal:

export JWT_VERIFICATION_KEY="your-public-key"

Or, if you manage keys via a JWKS file, point AgentOS at it instead:

export JWT_JWKS_FILE="/path/to/jwks.json"

When tokens are issued by the AgentOS control plane, it keeps the private key and your service only sees the public key. See Generate a Verification Key from the Control Plane for the full walkthrough.

Configure JWTs from Your Backend or IDP

If you're issuing JWTs from your own backend, or from a third-party identity provider like WorkOS, Auth0, or Okta, pass an AuthorizationConfig:

from agno.os import AgentOS
from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=["public-key-1", "public-key-2"],   # multi-issuer
        algorithm="RS256",
        verify_audience=True,
        audience="my-agent-os",   # must match the token's `aud`; defaults to the AgentOS id
        user_isolation=True,      # see below
    ),
)

verification_keys is a list. AgentOS tries each key in order until one verifies the token, so you can accept tokens from multiple issuers at the same time. For key rotation, a local JWKS file can select keys by kid; refresh the file and restart the application or reconstruct the validator after rotation. AgentOS loads the keys when the validator is constructed.

With verify_audience=True, AgentOS rejects tokens whose aud claim doesn't match the expected audience. That expected value defaults to the AgentOS id; set audience to override it when your provider mints a different value.

JWT claim names (scopes, sub) are configured on the JWT middleware itself, not on AuthorizationConfig. The defaults (scopes for the scopes claim, sub for the user ID claim) match the tokens minted by the control plane.

For the full self-hosted setup including multi-issuer, see Self-Hosted.

Authorization

AgentOS reads a JWT caller’s permissions from an array of scope strings in a claim (scopes by default). Service-account tokens use their stored scopes, which are enforced independently of the JWT authorization flag. If your provider uses a different name, such as WorkOS's permissions, set the scopes_claim argument on the JWT middleware. It is not a field on AuthorizationConfig. Endpoints are gated on those scopes.

ScopeGrants
agents:readList agents and read their config
agents:<id>:runRun a specific agent
agents:*:runRun any agent (same pattern for teams, workflows)
agent_os:adminFull access including session, memory, and trace queries

The AgentOS control plane mints each token with the appropriate scopes. Scopes are bundled into roles and assigned to users in the control plane: the AgentOS control plane provides three default roles (owner, admin, member), and custom roles are available on Enterprise. Self-hosters define roles in their identity provider or backend. See the scope reference for the full scope list, Default Roles for what each grants, and Custom Roles to compose your own.

Request isolation

Core run endpoints start each run from a fresh copy of the registered agent, team, or workflow. AgentOS calls deep_copy() and copies mutable fields when possible.

Models, databases, knowledge resources, MCP tool handles, and some tools are shared by reference so their connections and pools remain available. A field that cannot be copied also falls back to the original value. Custom tools and objects shared this way must be safe for concurrent use.

The copy happens automatically on core run endpoints. Review mutable state in custom tools and objects before serving concurrent traffic.

User isolation

For JWT callers, per-user data isolation is opt-in. Authorization remains active without it, but user-owned data is not automatically filtered by the JWT subject. Non-admin service-account tokens remain scoped to their sa:<name> identity even when this option is off. A caller with the required read scopes can query data across users unless another restriction applies. For multi-tenant deployments, turn it on:

from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=[public_key],
        user_isolation=True,   # for JWT callers, user_id comes from the verified sub
    ),
)

With user_isolation=True, every non-admin caller gets:

GuaranteeHow
User-owned readsThe authenticated principal is threaded as user_id; another user’s private rows are not returned. Some domains also expose shared rows with no owner.
User-owned writesuser_id is coerced to the authenticated principal. Scoped callers cannot mutate shared unowned rows.
Run ownershipCancel, resume, and continue routes require session_id and verify the run belongs to the caller's session.
WebSocket reconnectReconnecting to a workflow run requires session_id and workflow_id, then verifies the caller owns the run.

A service-account token executes as sa:<name>, separately from the account creator’s identity. See User isolation for principal and shared-data rules.

Admin callers with the configured admin_scope, which defaults to agent_os:admin, bypass user isolation and receive the unscoped view.

Per-user isolation requires a database that records user_id (PostgreSQL recommended for production).

Defaults

ConcernDefault behavior
Authenticationauthorization=False disables JWT scope enforcement. Configured JWT environment keys or OS_SECURITY_KEY still enable authentication; service-account credentials follow their own scoped verification. Set authorization=True with a JWT verification key to enable JWT RBAC.
Request isolationOn. Run endpoints deep-copy the registered component.
User isolationOff for JWT callers. Opt in via AuthorizationConfig(user_isolation=True). Non-admin service-account tokens are scoped to their own identity regardless of this flag.

See the AuthorizationConfig reference for all configuration options and their defaults.