diff --git a/agent-os/custom-fastapi/overview.mdx b/agent-os/custom-fastapi/overview.mdx index bdcbb35be..73027d9ea 100644 --- a/agent-os/custom-fastapi/overview.mdx +++ b/agent-os/custom-fastapi/overview.mdx @@ -171,6 +171,30 @@ if __name__ == "__main__": agent_os.serve(app="custom_fastapi_app:app", reload=True) ``` +Run it end to end: + + + + + + ```bash + export ANTHROPIC_API_KEY=your_anthropic_api_key + ``` + + + + ```bash + uv pip install -U agno anthropic fastapi uvicorn sqlalchemy + ``` + + + + ```bash + python custom_fastapi_app.py + ``` + + + ## Middleware and Dependencies @@ -234,5 +258,4 @@ for route in routes: ## Developer Resources - [AgentOS Reference](/reference/agent-os/agent-os) -- [Full Example](/agent-os/usage/custom-fastapi) - [FastAPI Documentation](https://fastapi.tiangolo.com/) diff --git a/agent-os/middleware/jwt.mdx b/agent-os/middleware/jwt.mdx index f262fd501..d563ca4cc 100644 --- a/agent-os/middleware/jwt.mdx +++ b/agent-os/middleware/jwt.mdx @@ -225,10 +225,30 @@ app.add_middleware( For detailed RBAC documentation including all available scopes and default mappings, see [RBAC Documentation](/agent-os/security/rbac). +## User Isolation + +RBAC controls which endpoints a caller can hit. User isolation controls which rows they can see and mutate. The two are independent toggles. + +```python jwt_with_user_isolation.py +app.add_middleware( + JWTMiddleware, + verification_keys=["your-jwt-key"], + authorization=True, + user_isolation=True, +) +``` + +When `user_isolation=True`, non-admin callers are scoped to their own `user_id` (from the JWT `sub` claim) for sessions, memory, and traces. Callers holding `admin_scope` bypass isolation. See [Per-User Data Isolation](/agent-os/security/rbac#per-user-data-isolation) for the full behavior. ## Excluded Routes -Skip middleware for specific routes: +These routes skip JWT and RBAC checks by default: + +```python +["/", "/health", "/info", "/docs", "/redoc", "/openapi.json", "/docs/oauth2-redirect"] +``` + +Override them with `excluded_route_paths`: ```python jwt_excluded_routes.py app.add_middleware( @@ -236,12 +256,17 @@ app.add_middleware( verification_keys=["your-key"], excluded_route_paths=[ "/health", - "/auth/login", + "/auth/login", "/auth/register", "/public/*", # Wildcards supported ] ) ``` + + +`excluded_route_paths` replaces the defaults, it is not additive. Re-include any default routes you want to keep. + + ## Configuration Options See the [JWTMiddleware Reference](/reference/agent-os/jwt-middleware) for the complete list of configuration options. @@ -282,6 +307,7 @@ See the [JWTMiddleware Reference](/reference/agent-os/jwt-middleware) for the co | `authorization` | Enable RBAC scope checking | `False` | | `verify_audience` | Verify `aud` claim matches AgentOS ID | `False` | | `audience` | Expected audience claim to validate against the token's audience claim | `AgentOS ID` | +| `user_isolation` | Opt in to per-user data isolation. When `True`, AgentOS uses the JWT `sub` claim as the `user_id` for every non-admin caller: reads are scoped to it and writes are coerced to it, so callers can't see or persist other users' rows. Callers holding `admin_scope` bypass it. | `False` | | `scope_mappings` | Custom route-to-scope mappings (additive to defaults) | `None` | | `admin_scope` | Scope that grants full admin access | `"agent_os:admin"` | | `excluded_route_paths` | Routes to skip JWT/RBAC checks | See below | diff --git a/agent-os/middleware/overview.mdx b/agent-os/middleware/overview.mdx index de71ab0b8..0302e1eeb 100644 --- a/agent-os/middleware/overview.mdx +++ b/agent-os/middleware/overview.mdx @@ -9,12 +9,12 @@ keywords: [middleware, fastapi middleware, agentos middleware, jwt middleware, c v2.1.0 -AgentOS is built on FastAPI, allowing you to add any [FastAPI/Starlette compatible middleware](https://fastapi.tiangolo.com/tutorial/middleware/) for authentication, logging, monitoring, and security. Agno provides built-in JWT middleware for authentication, and you can create custom middleware for rate limiting, request logging, and security headers. +AgentOS is built on FastAPI, so you can add any [FastAPI/Starlette-compatible middleware](https://fastapi.tiangolo.com/tutorial/middleware/) for authentication, logging, monitoring, and security. -Additionally, Agno provides some built-in middleware for common use cases, including authentication. +Agno ships a built-in JWT middleware for authentication. You can write your own custom middleware for rate limiting, request logging, and security headers. See the following guides: - + Built-in JWT authentication with automatic parameter injection and claims extraction. - - Use the built-in JWT middleware with Role-based access control and fine-grained permission scopes. - diff --git a/agent-os/security/rbac.mdx b/agent-os/security/rbac.mdx index 792236320..1a4fff019 100644 --- a/agent-os/security/rbac.mdx +++ b/agent-os/security/rbac.mdx @@ -42,7 +42,6 @@ app = agent_os.get_app() ``` - ## Generate a Verification Key `authorization=True` only tells AgentOS to enforce JWT authorization. To verify tokens, AgentOS also needs a public key. Generate one from the control plane and wire it in. diff --git a/agent-os/usage/custom-fastapi.mdx b/agent-os/usage/custom-fastapi.mdx deleted file mode 100644 index 80bc6f0fd..000000000 --- a/agent-os/usage/custom-fastapi.mdx +++ /dev/null @@ -1,118 +0,0 @@ ---- -title: "AgentOS Custom FastAPI" -description: "AgentOS where you provide your own FastAPI app" ---- - -Here is a full example of an AgentOS where you provide your own FastAPI app. - -## Code - -```python custom_fastapi.py -from agno.agent import Agent -from agno.db.postgres import PostgresDb -from agno.models.anthropic import Claude -from agno.os import AgentOS -from agno.tools.hackernews import HackerNewsTools -from fastapi import FastAPI - -# Setup the database -db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai") - -web_research_agent = Agent( - id="web-research-agent", - name="Web Research Agent", - model=Claude(id="claude-sonnet-4-0"), - db=db, - tools=[HackerNewsTools()], - add_history_to_context=True, - num_history_runs=3, - add_datetime_to_context=True, - markdown=True, -) - -# Custom FastAPI app -app: FastAPI = FastAPI( - title="Custom FastAPI App", - version="1.0.0", -) - - -# Add your own routes -@app.post("/customers") -async def get_customers(): - return [ - { - "id": 1, - "name": "John Doe", - "email": "john.doe@example.com", - }, - { - "id": 2, - "name": "Jane Doe", - "email": "jane.doe@example.com", - }, - ] - - -# Setup our AgentOS app by passing your FastAPI app -# Use route_prefix to avoid conflicts with your custom routes -agent_os = AgentOS( - description="Example app with custom routers", - agents=[web_research_agent], - base_app=app, -) - -# Alternatively, add all routes from AgentOS app to the current app -# for route in agent_os.get_routes(): -# app.router.routes.append(route) - -app = agent_os.get_app() - - -if __name__ == "__main__": - """Run your AgentOS. - - With this setup: - - API docs: http://localhost:7777/docs - - """ - agent_os.serve(app="custom_fastapi:app", reload=True) - -``` - -## Usage - - - - - - ```bash - export ANTHROPIC_API_KEY=your_anthropic_api_key - ``` - - - - ```bash - uv pip install -U agno anthropic fastapi uvicorn sqlalchemy pgvector psycopg - ``` - - - - ```bash - # Using Docker - docker run -d \ - --name agno-postgres \ - -e POSTGRES_DB=ai \ - -e POSTGRES_USER=ai \ - -e POSTGRES_PASSWORD=ai \ - -p 5532:5432 \ - pgvector/pgvector:pg17 - ``` - - - - ```bash - python custom_fastapi.py - ``` - - \ No newline at end of file diff --git a/videos/roles.mp4 b/images/roles.mp4 similarity index 100% rename from videos/roles.mp4 rename to images/roles.mp4 diff --git a/reference/agent-os/authorization-config.mdx b/reference/agent-os/authorization-config.mdx index fd51f7449..83e944c73 100644 --- a/reference/agent-os/authorization-config.mdx +++ b/reference/agent-os/authorization-config.mdx @@ -19,6 +19,7 @@ from agno.os.config import AuthorizationConfig | `jwks_file` | `Optional[str]` | `None` | Path to a static JWKS (JSON Web Key Set) file containing public keys. Keys are matched by `kid` (key ID) from the JWT header. Alternative to `verification_keys` for RSA key management. | | `algorithm` | `Optional[str]` | `RS256` | JWT algorithm for token verification. Common options: `RS256` (asymmetric), `HS256` (symmetric). | | `verify_audience` | `Optional[bool]` | `False` | Whether to verify the audience claim of the JWT token. This should not be enabled for AgentOS Control Plane traffic. | +| `user_isolation` | `bool` | `False` | Opt in to per-user data isolation. When `True`, AgentOS uses the JWT `sub` claim as the `user_id` for every non-admin caller: reads are scoped to it and writes are coerced to it, so callers can't see or persist other users' rows. Callers holding `admin_scope` bypass it. Requires a database that records `user_id`. | ## Usage diff --git a/reference/agent-os/jwt-middleware.mdx b/reference/agent-os/jwt-middleware.mdx index fc18e428e..8297bf115 100644 --- a/reference/agent-os/jwt-middleware.mdx +++ b/reference/agent-os/jwt-middleware.mdx @@ -21,7 +21,7 @@ from agno.os.middleware.jwt import TokenSource | `secret_key` | `Optional[str]` | `None` | **(Deprecated)** Use `verification_keys` instead. | | `algorithm` | `str` | `"RS256"` | JWT algorithm (RS256, HS256, ES256, etc.) | | `validate` | `bool` | `True` | Whether to validate JWT tokens | -| `authorization` | `Optional[bool]` | `None` | Enable RBAC scope checking | +| `authorization` | `Optional[bool]` | `None` | Enable RBAC scope checking. If left `None` and `scope_mappings` is provided, RBAC is auto-enabled. | | `token_source` | `TokenSource` | `TokenSource.HEADER` | Where to extract JWT token from | | `token_header_key` | `str` | `"Authorization"` | Header key for Authorization | | `cookie_name` | `str` | `"access_token"` | Cookie name for JWT token | @@ -29,13 +29,14 @@ from agno.os.middleware.jwt import TokenSource | `user_id_claim` | `str` | `"sub"` | JWT claim name for user ID | | `session_id_claim` | `str` | `"session_id"` | JWT claim name for session ID | | `audience_claim` | `str` | `"aud"` | JWT claim name for audience/OS ID | -| `audience` | `Optional[Union[str, Iterable[str]]]` | `None` | Expected audience claim to validate against the token's audience claim. Defaults to the AgentOS ID. | +| `audience` | `Optional[Union[str, Iterable[str]]]` | `None` | Expected audience(s) to validate the token's `aud` claim against. Accepts a string or a list of strings; the token matches if its audience matches any of them. Defaults to the AgentOS ID. | | `verify_audience` | `bool` | `False` | Verify `aud` claim matches AgentOS ID | | `dependencies_claims` | `Optional[List[str]]` | `None` | Claims to extract for `dependencies` parameter | | `session_state_claims` | `Optional[List[str]]` | `None` | Claims to extract for `session_state` parameter | | `scope_mappings` | `Optional[Dict[str, List[str]]]` | `None` | Custom route-to-scope mappings (additive to defaults) | -| `excluded_route_paths` | `Optional[List[str]]` | See below | Routes to skip JWT/RBAC checks | +| `excluded_route_paths` | `Optional[List[str]]` | See below | Routes to skip JWT/RBAC checks. | | `admin_scope` | `Optional[str]` | `"agent_os:admin"` | Scope that grants full admin access | +| `user_isolation` | `bool` | `False` | Opt in to per-user data isolation. When `True`, AgentOS uses the JWT `sub` claim as the `user_id` for every non-admin caller: reads are scoped to it and writes are coerced to it, so callers can't see or persist other users' rows. Callers holding `admin_scope` bypass it. Requires a database that records `user_id`. | ## TokenSource Enum @@ -51,6 +52,7 @@ from agno.os.middleware.jwt import TokenSource [ "/", "/health", + "/info", "/docs", "/redoc", "/openapi.json",