From f0d3b1d09d5847699ac127adc69a66b24481d890 Mon Sep 17 00:00:00 2001 From: Monalisha Mishra Date: Wed, 3 Jun 2026 15:25:53 +0530 Subject: [PATCH 1/4] auth doc rvamp --- agent-os/api/authentication.mdx | 8 +- agent-os/control-plane.mdx | 4 +- agent-os/middleware/jwt.mdx | 27 +- agent-os/middleware/overview.mdx | 14 +- agent-os/overview.mdx | 2 +- .../security/authorization/customization.mdx | 41 ++ agent-os/security/authorization/overview.mdx | 135 ++++ .../security/authorization/quickstart.mdx | 133 ++++ agent-os/security/authorization/roles.mdx | 65 ++ agent-os/security/authorization/scopes.mdx | 230 +++++++ .../security/authorization/self-hosted.mdx | 137 ++++ agent-os/security/authorization/tokens.mdx | 73 +++ .../security/authorization/user-isolation.mdx | 45 ++ agent-os/security/overview.mdx | 24 +- agent-os/security/rbac.mdx | 594 ------------------ docs.json | 32 +- faq/rbac-auth-failed.mdx | 6 +- features/security-and-auth.mdx | 27 +- reference/agent-os/authorization-config.mdx | 2 +- reference/agent-os/jwt-middleware.mdx | 2 +- tutorials/dash/deploy-to-railway.mdx | 2 +- tutorials/scout/deploy-to-railway.mdx | 2 +- 22 files changed, 954 insertions(+), 651 deletions(-) create mode 100644 agent-os/security/authorization/customization.mdx create mode 100644 agent-os/security/authorization/overview.mdx create mode 100644 agent-os/security/authorization/quickstart.mdx create mode 100644 agent-os/security/authorization/roles.mdx create mode 100644 agent-os/security/authorization/scopes.mdx create mode 100644 agent-os/security/authorization/self-hosted.mdx create mode 100644 agent-os/security/authorization/tokens.mdx create mode 100644 agent-os/security/authorization/user-isolation.mdx delete mode 100644 agent-os/security/rbac.mdx diff --git a/agent-os/api/authentication.mdx b/agent-os/api/authentication.mdx index e13d094c2..cdea6d2af 100644 --- a/agent-os/api/authentication.mdx +++ b/agent-os/api/authentication.mdx @@ -49,7 +49,7 @@ Your JWT tokens should include scopes and audience claims: | `sessions:write` | Create/update sessions | | `agent_os:admin` | Full admin access | -See [RBAC Documentation](/agent-os/security/rbac) for all available scopes. +See [Scopes](/agent-os/security/authorization/scopes) for all available scopes. ### Error Responses @@ -67,12 +67,12 @@ See [RBAC Documentation](/agent-os/security/rbac) for all available scopes. icon="shield" href="/agent-os/security/overview" > - Enable RBAC and configure authorization. + Enable authorization and configure JWT verification. Complete scope reference and endpoint mappings. diff --git a/agent-os/control-plane.mdx b/agent-os/control-plane.mdx index 16ef846bc..6702d87dc 100644 --- a/agent-os/control-plane.mdx +++ b/agent-os/control-plane.mdx @@ -254,7 +254,7 @@ Enable JWT verification and generate key pairs from the control plane, either wh -See [AgentOS Security](/agent-os/security/overview) and [RBAC](/agent-os/security/rbac) for setup details. +See [AgentOS Security](/agent-os/security/overview) and [Authorization](/agent-os/security/authorization/overview) for setup details. ## User Management @@ -268,7 +268,7 @@ Every organization has three default roles. Custom roles are available on the En | **Administrator** | Manage members, roles, settings, and resources; cannot update billing or delete the organization | | **Member** | Run and edit AgentOS resources; cannot delete resources or manage members | -See [RBAC](/agent-os/security/rbac) for the full capability matrix and custom role setup. +See [Roles](/agent-os/security/authorization/roles) for the full capability matrix and custom role setup. -## Configuration Options +## Configurable Options Configure JWT verification using `AuthorizationConfig`: From ae5e4e5d4e813c298caa95764c4f9b592762e4b5 Mon Sep 17 00:00:00 2001 From: Monalisha Mishra Date: Fri, 5 Jun 2026 13:15:57 +0530 Subject: [PATCH 4/4] workos example --- .../security/authorization/self-hosted.mdx | 2 + agent-os/usage/rbac/workos-byot.mdx | 258 ++++++++++++++++++ docs.json | 1 + 3 files changed, 261 insertions(+) create mode 100644 agent-os/usage/rbac/workos-byot.mdx diff --git a/agent-os/security/authorization/self-hosted.mdx b/agent-os/security/authorization/self-hosted.mdx index 3137e1afe..610255d71 100644 --- a/agent-os/security/authorization/self-hosted.mdx +++ b/agent-os/security/authorization/self-hosted.mdx @@ -96,6 +96,8 @@ WorkOS access tokens carry `permissions`, `role`, `org_id`, and `sid` claims by **Why this example uses `JWTMiddleware` instead of `AuthorizationConfig`.** `AuthorizationConfig` covers verification keys, algorithm, audience, and isolation, but not claim names or token sources. Use it when the provider's JWT uses the standard `scopes` and `sub` claims. For providers that use a different claim name (WorkOS `permissions`, Auth0 `scope`, Okta `scp`), or when you need cookie tokens or custom scope mappings, use `JWTMiddleware` directly. +See [WorkOS BYOT](/agent-os/usage/rbac/workos-byot) for a runnable example. + ## Accepting Multiple Issuers `verification_keys` is a list. AgentOS tries each key in order until one verifies the token. Pass a second key to accept tokens from both the AgentOS control plane and your own backend at the same time. diff --git a/agent-os/usage/rbac/workos-byot.mdx b/agent-os/usage/rbac/workos-byot.mdx new file mode 100644 index 000000000..9fbdc80a6 --- /dev/null +++ b/agent-os/usage/rbac/workos-byot.mdx @@ -0,0 +1,258 @@ +--- +title: WorkOS BYOT (Bring Your Own Token) +sidebarTitle: WorkOS BYOT +--- + +Use WorkOS as the JWT issuer for AgentOS. WorkOS signs tokens with its keys; AgentOS verifies them against the WorkOS JWKS and reads scopes from the `permissions` claim. + +This example provisions everything via the WorkOS API: permissions, three roles (`admin`, `member`, `viewer`), one organization, and three users. It mints a real WorkOS-signed access token per user and prints a curl command for each. + + + + + ```python workos_byot.py + """ + WorkOS BYOT with AgentOS - 3 roles, real WorkOS tokens, RBAC provisioned via API. + + Roles (permission slugs match AgentOS scopes): + - admin -> agent_os:admin (full access) + - member -> agents:read, agents:run, sessions:read (can list/run agents) + - viewer -> sessions:read (no agents:read -> 403 on /agents) + + One-time WorkOS dashboard prerequisites: + - Enable RBAC (so permissions/roles can be created). + - Enable Email + Password authentication (so the password grant works). + """ + + import os + + import httpx + import jwt + from agno.agent import Agent + from agno.db.sqlite import SqliteDb + from agno.models.openai import OpenAIResponses + from agno.os import AgentOS + from agno.os.middleware.jwt import JWTMiddleware + from workos import WorkOSClient + from workos.organization_membership._resource import RoleSingle + from workos.user_management import PasswordPlaintext + + + def _env(name: str) -> str | None: + value = os.getenv(name) + return value.strip().strip("\"'").strip() if value else None + + + WORKOS_CLIENT_ID = _env("WORKOS_CLIENT_ID") + WORKOS_API_KEY = _env("WORKOS_API_KEY") + if not WORKOS_CLIENT_ID or not WORKOS_API_KEY: + raise SystemExit( + "Set WORKOS_CLIENT_ID and WORKOS_API_KEY (from the same WorkOS " + "environment) before running." + ) + + _JWKS_FILE = "/tmp/agno_workos_jwks.json" + + workos = WorkOSClient(api_key=WORKOS_API_KEY, client_id=WORKOS_CLIENT_ID) + + # RBAC definition - permission slugs match AgentOS scope names. + ORG_NAME = "Agno BYOT Demo" + DEMO_DOMAIN = "agno-byot-demo.com" + DEMO_PASSWORD = "Agno-Demo-Passw0rd!" + + PERMISSIONS = ["agents:read", "agents:run", "sessions:read", "agent_os:admin"] + ROLES = { + "admin": ["agent_os:admin"], + "member": ["agents:read", "agents:run", "sessions:read"], + "viewer": ["sessions:read"], + } + USERS = [ + ("admin", "admin", "admin"), + ("member", "member", "member"), + ("viewer", "viewer", "viewer"), + ] + + + def _download_workos_jwks(client_id: str, dest: str) -> str: + """Fetch the public WorkOS JWKS and write it to a local file. + `jwks_file` requires a local path, not a URL.""" + url = f"https://api.workos.com/sso/jwks/{client_id}" + response = httpx.get(url, timeout=10.0) + response.raise_for_status() + with open(dest, "w") as f: + f.write(response.text) + return dest + + + # RBAC provisioning via the WorkOS API (idempotent). Demo-only. + # In production, your users log in through your existing WorkOS flow; AgentOS + # only has to verify the token (see the JWTMiddleware setup below). + + def _ensure_permissions() -> None: + for slug in PERMISSIONS: + try: + workos.authorization.create_permission(slug=slug, name=slug) + except Exception: + pass + + + def _ensure_roles() -> None: + for slug, perms in ROLES.items(): + try: + workos.authorization.create_environment_role(slug=slug, name=slug) + except Exception: + pass + workos.authorization.set_environment_role_permissions(slug, permissions=perms) + + + def _ensure_org() -> str: + for org in workos.organizations.list_organizations(limit=100).data: + if org.name == ORG_NAME: + return org.id + return workos.organizations.create_organization(name=ORG_NAME).id + + + def _ensure_user(email: str) -> str: + password = PasswordPlaintext(password=DEMO_PASSWORD) + try: + return workos.user_management.create_user( + email=email, password=password, email_verified=True + ).id + except Exception: + user_id = workos.user_management.list_users(email=email).data[0].id + workos.user_management.update_user(user_id, password=password) + return user_id + + + def _ensure_membership(user_id: str, org_id: str, role_slug: str) -> None: + try: + workos.organization_membership.create_organization_membership( + user_id=user_id, + organization_id=org_id, + role=RoleSingle(role_slug=role_slug), + ) + except Exception: + pass + + + def _mint_token(email: str) -> str: + """Mint a real WorkOS access token via the password grant. + Single-org users get org-scoped tokens that carry permissions.""" + auth = workos.user_management.authenticate_with_password( + email=email, password=DEMO_PASSWORD + ) + return auth.access_token + + + # AgentOS configured to verify WorkOS tokens via JWKS + `permissions` claim. + # This is the actual WorkOS integration - the only part needed in production. + + _download_workos_jwks(WORKOS_CLIENT_ID, _JWKS_FILE) + + db = SqliteDb(db_file="tmp/workos_byot.db") + + research_agent = Agent( + id="research-agent", + name="Research Agent", + model=OpenAIResponses(id="gpt-5-mini"), + db=db, + add_history_to_context=True, + markdown=True, + ) + + # AuthorizationConfig can't set claim names. WorkOS uses `permissions` + # (not the default `scopes`), so JWTMiddleware is required. + agent_os = AgentOS( + id="my-agent-os", + description="AgentOS verifying WorkOS-issued tokens (BYOT)", + agents=[research_agent], + ) + + app = agent_os.get_app() + + app.add_middleware( + JWTMiddleware, + jwks_file=_JWKS_FILE, + algorithm="RS256", + scopes_claim="permissions", + admin_scope="agent_os:admin", + authorization=True, + ) + + + if __name__ == "__main__": + print("\n" + "=" * 70) + print("WorkOS BYOT - provisioning RBAC (permissions, roles, org, users)") + print("=" * 70) + + _ensure_permissions() + _ensure_roles() + org_id = _ensure_org() + print("Organization: " + ORG_NAME + " (" + org_id + ")") + + tokens = [] + for label, local_part, role_slug in USERS: + email = f"{local_part}@{DEMO_DOMAIN}" + user_id = _ensure_user(email) + _ensure_membership(user_id, org_id, role_slug) + token = _mint_token(email) + perms = jwt.decode(token, options={"verify_signature": False}).get( + "permissions", [] + ) + tokens.append((label, role_slug, perms, token)) + print(f"Provisioned {label:7} {email:32} role={role_slug} perms={perms}") + + print("\n" + "=" * 70) + print("Test commands (each token signed by WorkOS, verified via JWKS)") + print("=" * 70) + for label, role_slug, perms, token in tokens: + print(f"\n# {label} ({role_slug}, permissions={perms}):") + print( + f'curl -i -H "Authorization: Bearer {token}" http://localhost:7777/agents' + ) + + print("\n# No token -> 401:") + print("curl -i http://localhost:7777/agents") + + agent_os.serve(app="workos_byot:app", port=7777, reload=True) + ``` + + + + + + ```bash + uv pip install -U agno openai pyjwt workos httpx "fastapi[standard]" uvicorn sqlalchemy + ``` + + + + Both must come from the **same** WorkOS environment: + + ```bash + export WORKOS_API_KEY="sk_..." + export WORKOS_CLIENT_ID="client_..." + export OPENAI_API_KEY="your_openai_api_key_here" + ``` + + + + ```bash + python workos_byot.py + ``` + + The script provisions RBAC in WorkOS, mints a WorkOS-signed token per user, and prints a curl command for each. + + + + Run the curl commands printed by the script. Expected outcomes on `GET /agents`: + + | User | Permissions | Result | + |------|-------------|--------| + | admin | `agent_os:admin` | `200 OK` | + | member | `agents:read`, `agents:run`, `sessions:read` | `200 OK` | + | viewer | `sessions:read` | `403 Forbidden` | + | (no token) | — | `401 Unauthorized` | + + + diff --git a/docs.json b/docs.json index b244b211b..a4e07dd72 100644 --- a/docs.json +++ b/docs.json @@ -4611,6 +4611,7 @@ "pages": [ "agent-os/usage/rbac/basic-symmetric", "agent-os/usage/rbac/basic-asymmetric", + "agent-os/usage/rbac/workos-byot", "agent-os/usage/rbac/advanced-scopes", "agent-os/usage/rbac/per-agent-permissions", "agent-os/usage/rbac/custom-scope-mappings"