# Security & Auth (/features/security-and-auth)



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.

| Boundary             | Control                                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------------------- |
| Caller identity      | JWT signature verification; set `verify_audience=True` to enforce the `aud` claim                    |
| API access           | Scope checks on mapped endpoints and built-in resource handlers; add mappings for custom REST routes |
| Run state            | Fresh component copy for core run endpoints, with some resources shared by reference                 |
| Persistent user data | Opt-in reads, writes, and ownership checks scoped to the JWT subject                                 |
| Network and database | Reverse proxy controls and database permissions configured by the deployment                         |

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

## Authentication [#authentication]

A production AgentOS sits behind JWT-validating middleware. Tokens can come from the [AgentOS control plane](https://os.agno.com), your own backend, or a third-party identity provider. Your service verifies them with the matching public key. See [Self-Hosted](/agent-os/security/authorization/self-hosted) for BYO and third-party setup.

```python
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](/agent-os/security/overview#surface-specific-policies).

### Generate a Verification Key from the Control Plane [#generate-a-verification-key-from-the-control-plane]

<Steps>
  <Step title="Toggle JWT authorization">
    Open [os.agno.com](https://os.agno.com) → **Connect OS** → **Live** → paste your URL. Enable JWT authorization when connecting a new AgentOS, or later from the OS Settings page.
  </Step>

  <Step title="Copy the public key">
    Copy the public key for your AgentOS from the modal.
  </Step>

  <Step title="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:

    ```bash
    export JWT_VERIFICATION_KEY="your-public-key"
    ```

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

    ```bash
    export JWT_JWKS_FILE="/path/to/jwks.json"
    ```
  </Step>
</Steps>

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](/agent-os/security/authorization/quickstart#generate-a-verification-key-from-the-control-plane) for the full walkthrough.

### Configure JWTs from Your Backend or IDP [#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`:

```python
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](/agent-os/security/authorization/self-hosted).

## Authorization [#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.

| Scope             | Grants                                                   |
| ----------------- | -------------------------------------------------------- |
| `agents:read`     | List agents and read their config                        |
| `agents:<id>:run` | Run a specific agent                                     |
| `agents:*:run`    | Run any agent (same pattern for teams, workflows)        |
| `agent_os:admin`  | Full 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](/agent-os/security/authorization/scopes#scope-reference) for the full scope list, [Default Roles](/agent-os/security/authorization/roles#default-roles) for what each grants, and [Custom Roles](/agent-os/security/authorization/roles#custom-roles) to compose your own.

## Request isolation [#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 [#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:

```python
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:

| Guarantee               | How                                                                                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **User-owned reads**    | The 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 writes**   | `user_id` is coerced to the authenticated principal. Scoped callers cannot mutate shared unowned rows.                                                  |
| **Run ownership**       | Cancel, resume, and continue routes require `session_id` and verify the run belongs to the caller's session.                                            |
| **WebSocket reconnect** | Reconnecting 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](/agent-os/security/authorization/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 [#defaults]

| Concern           | Default behavior                                                                                                                                                                                                                                                                   |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authentication    | `authorization=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 isolation | On. Run endpoints deep-copy the registered component.                                                                                                                                                                                                                              |
| User isolation    | **Off 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](/reference/agent-os/authorization-config) for all configuration options and their defaults.
