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

# Security & Auth

> Build a layered security model: authenticate every request, use RBAC for roles, and isolate data and access between users.

AgentOS security works in layers. Each one stops a different class of attack, from unauthenticated requests at the edge to cross-user data access in the database.

| Layer                 | What it stops                                 | Mechanism                                               |
| --------------------- | --------------------------------------------- | ------------------------------------------------------- |
| **Authentication**    | Unauthenticated requests                      | JWT validation at the middleware                        |
| **Authorization**     | Authenticated but out-of-scope requests       | RBAC scopes on every endpoint                           |
| **Request isolation** | Concurrent requests reading each other's data | Fresh, isolated object per request                      |
| **User isolation**    | Cross-user data leakage on shared endpoints   | Opt-in using `AuthorizationConfig(user_isolation=True)` |

AgentOS give you good defaults at every layer

<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. Every request carries a token signed by the [control plane](https://os.agno.com); your service verifies it with a public key.

```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 every request.
* **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`.

### Get a JWT\_VERIFICATION\_KEY

<Steps>
  <Step title="Toggle JWT authorization">
    Open [os.agno.com](https://os.agno.com) → **Add 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>

The control plane keeps the private key. Your service only ever sees the public key. JWTs are signed by the control plane and verified by your service. See [Generate a Verification Key](/agent-os/security/rbac#generate-a-verification-key) for the full walkthrough.

### Custom JWT configuration

If you're issuing JWTs from your own auth provider, 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"],   # rotation
        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, so you can rotate keys without downtime. Old tokens validate against the old key, and new tokens against the new. Drop the old key once tokens have expired.

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.

## Authorization (RBAC scopes)

Every JWT carries a `scopes` claim. Endpoints are gated on 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 control plane mints each token with the appropriate scopes. Scopes are bundled into roles and assigned to users in the control plane: AgentOS ships three default roles (owner, admin, member), and custom roles are available on Enterprise. See the [RBAC scope reference](/agent-os/security/rbac#scope-reference) for the full scope list, [Default Roles](/agent-os/security/rbac#default-roles) for what each grants, and [Custom Roles](/agent-os/security/rbac#custom-roles-and-scopes) to compose your own.

## Request isolation

Every request gets a fresh copy of the agent, team, or workflow it's hitting. AgentOS calls `deep_copy()` on the registered component per request, so mutable state (session-scoped variables, tool execution context, run metadata) never bleeds between concurrent calls.

Heavy resources (the DB connection, the model client, MCP tool handles) are shared by reference; only the mutable per-run state is isolated. You get cheap concurrency without the footgun of two requests racing on the same in-memory agent instance.

This is on by default for every run endpoint. There's nothing to configure.

## User isolation

Per-user data isolation is **opt-in**. RBAC stays in force without it, but routes operate on the unscoped DB. A caller with `agents:my-agent:run` could read another user's sessions if they know the IDs. 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 (whoever holds the configured `admin_scope`, defaulting to `agent_os:admin`) bypass all of the above and see the full unscoped view: service accounts, internal tooling, the control plane.

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. Every run endpoint deep-copies the 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.
