Skip to main content
AgentOS provides enterprise-grade security with JWT token authentication and fine-grained role-based access control (RBAC) for protecting your agent endpoints.

Overview

AgentOS supports two authentication methods:
  1. Security Key Authentication - Simple shared secret for development
  2. JWT Authentication - Token-based authentication with RBAC for production
This guide focuses on JWT authentication with RBAC, which is recommended for production deployments.

JWT Authentication

Basic Setup

Enable JWT authentication when creating your AgentOS:
from agno.os import AgentOS
from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    id="my-agent-os",
    agents=[agent],
    authorization=True,  # Enable RBAC
    authorization_config=AuthorizationConfig(
        verification_keys=["your-public-key-or-secret"],
        algorithm="RS256",  # Default: RS256 (asymmetric)
        verify_audience=True,  # Verify aud claim
    ),
)

app = agent_os.get_app()
When authorization=True, JWT middleware enforces scope-based access control. Without this flag, tokens are validated but all authenticated users can access all resources.

Using Environment Variables

Configure JWT via environment variables:
# Verification key (public key for RS256, secret for HS256)
export JWT_VERIFICATION_KEY="your-verification-key"

# Or use JWKS file for key rotation
export JWT_JWKS_FILE="/path/to/jwks.json"
Then enable authorization:
agent_os = AgentOS(
    id="my-agent-os",
    agents=[agent],
    authorization=True,  # Keys read from env vars
)

Signing Algorithms

AgentOS supports both symmetric and asymmetric JWT signing:
AlgorithmTypeUse CaseKey Type
RS256 (default)AsymmetricProductionRSA public/private key pair
HS256SymmetricDevelopmentShared secret
Use public/private key pairs for production:
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# Generate private key
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)

# Export private key (keep secure!)
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),
)

# Export public key (share with AgentOS)
public_pem = private_key.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

print("Private Key (for signing):")
print(private_pem.decode())
print("\nPublic Key (for verification):")
print(public_pem.decode())

HS256 (Symmetric) - Development

Use shared secrets for development and testing:
import jwt
from datetime import datetime, timedelta, UTC

# Shared secret (at least 256 bits)
SECRET = "a-very-long-secret-key-at-least-256-bits-long"

# Sign token
token = jwt.encode(
    {
        "sub": "user_123",
        "aud": "my-agent-os",
        "scopes": ["agents:read"],
        "exp": datetime.now(UTC) + timedelta(hours=24),
    },
    SECRET,
    algorithm="HS256"
)

# Configure AgentOS with same secret
agent_os = AgentOS(
    id="my-agent-os",
    agents=[agent],
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=[SECRET],
        algorithm="HS256",
    ),
)
HS256 requires both signing and verification to use the same secret. Only use in trusted environments where you control both token generation and verification.

Role-Based Access Control (RBAC)

Scope Format

AgentOS uses a hierarchical scope system:
1. Admin scope (highest privilege)
   agent_os:admin

2. Global resource scopes
   resource:action
   Examples: agents:read, teams:run

3. Per-resource scopes
   resource:resource_id:action
   Examples: agents:my-agent:run, teams:my-team:read

4. Wildcard scopes
   resource:*:action
   Examples: agents:*:run (run any agent)

Common Scopes

# Full access to everything
scopes = ["agent_os:admin"]

Available Scopes

ScopeDescription
system:readView system configuration
agents:readList all agents
agents:runRun any agent (global)
agents:{id}:readView specific agent
agents:{id}:runRun specific agent
agents:*:runRun any agent (wildcard)
teams:readList all teams
teams:runRun any team (global)
teams:{id}:runRun specific team
workflows:readList all workflows
workflows:runRun any workflow (global)
sessions:readView sessions
sessions:writeCreate/update sessions
sessions:deleteDelete sessions
memory:readView memory
knowledge:readView knowledge bases
schedules:readView schedules
schedules:writeCreate schedules
approvals:readView approvals
approvals:writeApprove/reject requests

Creating JWT Tokens

Token Structure

A valid JWT token for AgentOS must include:
import jwt
from datetime import datetime, timedelta, UTC

payload = {
    # Required claims
    "sub": "user_123",           # Subject (user ID)
    "aud": "my-agent-os",        # Audience (AgentOS ID)
    "exp": datetime.now(UTC) + timedelta(hours=24),  # Expiration
    "iat": datetime.now(UTC),    # Issued at
    
    # RBAC scopes
    "scopes": [
        "agents:read",
        "agents:my-agent:run",
    ],
    
    # Optional custom claims
    "email": "[email protected]",
    "name": "John Doe",
    "roles": ["admin", "user"],
}

token = jwt.encode(payload, SECRET_OR_PRIVATE_KEY, algorithm="RS256")

Audience Verification

The aud (audience) claim must match your AgentOS ID:
# AgentOS ID
agent_os = AgentOS(
    id="production-os",  # <-- Must match aud claim
    agents=[agent],
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=[public_key],
        verify_audience=True,  # Enable audience verification
    ),
)

# Token with matching audience
token = jwt.encode(
    {
        "sub": "user_123",
        "aud": "production-os",  # <-- Must match AgentOS ID
        "scopes": ["agents:read"],
        "exp": datetime.now(UTC) + timedelta(hours=24),
    },
    private_key,
    algorithm="RS256"
)
Tokens with mismatched aud claims will be rejected with a 401 error when verify_audience=True.

Making Authenticated Requests

Using Bearer Token

Include the JWT token in the Authorization header:
curl -X POST http://localhost:7777/agents/my-agent/runs \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -F "message=Hello!"

Response Filtering

Endpoints automatically filter results based on user scopes:
# User with specific agent access
# Scopes: ["agents:agent-1:read", "agents:agent-2:read"]
curl -H "Authorization: Bearer TOKEN" \
  http://localhost:7777/agents

# Response: Only returns agent-1 and agent-2
{
  "agents": [
    {"id": "agent-1", "name": "Agent 1"},
    {"id": "agent-2", "name": "Agent 2"}
  ]
}

# User with global read access
# Scopes: ["agents:read"]
curl -H "Authorization: Bearer TOKEN" \
  http://localhost:7777/agents

# Response: Returns all agents

Middleware Configuration

Direct Middleware Usage

Alternatively, add JWT middleware directly to your FastAPI app:
from agno.os import AgentOS
from agno.os.middleware import JWTMiddleware

agent_os = AgentOS(
    id="my-agent-os",
    agents=[agent],
)

app = agent_os.get_app()

# Add JWT middleware with custom configuration
app.add_middleware(
    JWTMiddleware,
    verification_keys=[public_key],
    algorithm="RS256",
    authorization=True,  # Enable RBAC
    verify_audience=True,
    user_id_claim="sub",
    session_id_claim="session_id",
    dependencies_claims=["email", "name"],  # Extract to request.state
)

Custom Scope Mappings

Override default scope requirements for endpoints:
app.add_middleware(
    JWTMiddleware,
    verification_keys=[public_key],
    authorization=True,
    scope_mappings={
        # Override default
        "GET /agents": ["custom:list_agents"],
        
        # Custom endpoint
        "POST /custom/endpoint": ["custom:action"],
        
        # Public endpoint (no auth required)
        "GET /public/health": [],
    }
)

Extracting Claims

Access JWT claims in your application:
from fastapi import Request

@app.get("/me")
async def get_current_user(request: Request):
    return {
        "user_id": request.state.user_id,
        "email": request.state.email,
        "name": request.state.name,
        "scopes": request.state.scopes,
    }

Security Best Practices

Use RS256 in Production

Always use asymmetric keys (RS256) for production deployments

Enable Audience Verification

Set verify_audience=True to prevent token reuse across services

Short Token Expiration

Use short-lived tokens (1-24 hours) and implement refresh tokens

Principle of Least Privilege

Grant minimum required scopes to each user

Token Best Practices

  1. Use HTTPS: Always use HTTPS in production to prevent token interception
  2. Rotate Keys: Implement key rotation using JWKS (JSON Web Key Sets)
  3. Monitor Access: Log authentication events and monitor for suspicious activity
  4. Validate Claims: Always verify exp, aud, and other critical claims
  5. Secure Storage: Never store tokens in localStorage; use httpOnly cookies

JWKS for Key Rotation

Use JWKS files for seamless key rotation:
jwks.json
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "key-1",
      "n": "...",
      "e": "AQAB"
    },
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "key-2",
      "n": "...",
      "e": "AQAB"
    }
  ]
}
agent_os = AgentOS(
    agents=[agent],
    authorization=True,
    authorization_config=AuthorizationConfig(
        jwks_file="/path/to/jwks.json",
        algorithm="RS256",
    ),
)

Complete Example

Here’s a complete example with JWT authentication and RBAC:
app.py
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIChat
from agno.os import AgentOS
from agno.os.config import AuthorizationConfig

# Read public key from file
with open("public_key.pem") as f:
    public_key = f.read()

# Setup database
db = PostgresDb(db_url="postgresql+psycopg://user:pass@localhost/agno")

# Create agents
research_agent = Agent(
    id="research-agent",
    name="Research Agent",
    model=OpenAIChat(id="gpt-4o"),
    db=db,
)

support_agent = Agent(
    id="support-agent",
    name="Support Agent",
    model=OpenAIChat(id="gpt-4o"),
    db=db,
)

# Create AgentOS with JWT authentication
agent_os = AgentOS(
    id="production-os",
    description="Production AgentOS with RBAC",
    agents=[research_agent, support_agent],
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=[public_key],
        algorithm="RS256",
        verify_audience=True,
    ),
    cors_allowed_origins=["https://yourdomain.com"],
)

app = agent_os.get_app()

if __name__ == "__main__":
    agent_os.serve(app="app:app", port=7777)
Generate and use tokens:
generate_token.py
import jwt
from datetime import datetime, timedelta, UTC

# Read private key
with open("private_key.pem") as f:
    private_key = f.read()

# Create token for admin user
admin_token = jwt.encode(
    {
        "sub": "admin_user",
        "aud": "production-os",
        "scopes": ["agent_os:admin"],
        "exp": datetime.now(UTC) + timedelta(hours=24),
        "iat": datetime.now(UTC),
    },
    private_key,
    algorithm="RS256"
)

# Create token for limited user
limited_token = jwt.encode(
    {
        "sub": "user_123",
        "aud": "production-os",
        "scopes": [
            "agents:research-agent:read",
            "agents:research-agent:run",
        ],
        "exp": datetime.now(UTC) + timedelta(hours=24),
        "iat": datetime.now(UTC),
    },
    private_key,
    algorithm="RS256"
)

print(f"Admin token: {admin_token}")
print(f"Limited token: {limited_token}")

Error Responses

401 Unauthorized

Token is missing, expired, or invalid:
{
  "detail": "Invalid or expired token"
}
Common causes:
  • Missing Authorization header
  • Expired token (exp claim in the past)
  • Invalid signature
  • Mismatched audience when verify_audience=True

403 Forbidden

User lacks required scopes:
{
  "detail": "Access denied to run this agent"
}
Common causes:
  • Missing required scope for the endpoint
  • Trying to access a resource not in user’s scopes
  • Token has agents:read but endpoint requires agents:run

Troubleshooting

Common authentication issues and solutions
Scopes not being enforced
  • Ensure authorization=True is set
  • Without this flag, JWT is validated but scopes are not checked
All resources return empty
  • Check that user has appropriate read scopes
  • Verify scope format matches the resource type
Audience verification fails
  • Ensure aud claim in token matches AgentOS id
  • Set verify_audience=False to disable (not recommended for production)
Token signature verification fails
  • For RS256: Ensure public key matches the private key used for signing
  • For HS256: Ensure the same secret is used for signing and verification

Next Steps

API Reference

Complete endpoint and authentication documentation

Deployment Guide

Deploy secured AgentOS to production

Build docs developers (and LLMs) love