> ## Documentation Index
> Fetch the complete documentation index at: https://phidatainc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

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

| Boundary             | Control                                                                              |
| -------------------- | ------------------------------------------------------------------------------------ |
| Caller identity      | JWT signature verification; set `verify_audience=True` to enforce the `aud` claim    |
| API access           | Scopes enforced per endpoint                                                         |
| 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

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 theme={null}
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): requires a valid JWT on protected central routes.
* **Authorization** (below): enforces the token's scopes per endpoint.

A small set of public 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.

### 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 theme={null}
    export JWT_VERIFICATION_KEY="your-public-key"
    ```

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

    ```bash theme={null}
    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

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 theme={null}
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, use a JWKS file instead.

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

AgentOS reads the caller's permissions from a JWT claim (`scopes` by default). 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

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

Per-user data isolation is **opt-in**. Authorization remains active without it, but user-scoped database reads are not automatically filtered by the JWT subject. A caller with session or memory read scopes can query rows across users unless another restriction applies. For multi-tenant deployments, turn it on:

```python theme={null}
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,   # requires authorization=True; the user_id comes from the JWT sub
    ),
)
```

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

| Guarantee                | How                                                                                                                           |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| **No cross-user reads**  | The JWT `sub` is threaded as `user_id` on every user-scoped read (sessions, memory, traces). Callers only see their own rows. |
| **No cross-user writes** | `user_id` is coerced on every write, so a non-admin can't persist a session, memory, or trace attributed to another user.     |
| **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.                |

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

| Concern           | Default behavior                                                                                   |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| Authentication    | `authorization=False`. Opt in with `authorization=True` and `JWT_VERIFICATION_KEY` for production. |
| Request isolation | On. Run endpoints deep-copy the registered component.                                              |
| User isolation    | **Off**. Opt in via `AuthorizationConfig(user_isolation=True)` for multi-tenant.                   |

See the [AuthorizationConfig reference](/reference/agent-os/authorization-config) for all configuration options and their defaults.
