JWT Middleware with Cookies

AgentOS with JWT middleware using HTTP-only cookies for secure web authentication

AuthMiddleware reads the JWT from an HTTP-only cookie instead of the Authorization header. HttpOnly prevents JavaScript from reading the cookie, but injected scripts can still make authenticated requests.

AuthMiddleware was named JWTMiddleware before v2.7. JWTMiddleware still works as an alias.

Code

This is a local authentication demonstration. The public /set-auth-cookie endpoint issues a token for a fixed test user without a login. Replace it with your authenticated login flow before deploying. Use HTTPS outside localhost.

jwt_cookies.py
from datetime import UTC, datetime, timedelta

import jwt
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS
from agno.run import RunContext
from agno.os.middleware.jwt import AuthMiddleware, TokenSource
from fastapi import FastAPI, Response

# JWT Secret (use environment variable in production)
JWT_SECRET = "a-string-secret-at-least-256-bits-long"

# Setup database
db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")


def get_user_profile(run_context: RunContext) -> dict:
    """
    Get the current user's profile.
    """
    dependencies = run_context.dependencies or {}
    return {
        "name": dependencies.get("name", "Unknown"),
        "email": dependencies.get("email", "Unknown"),
        "roles": dependencies.get("roles", []),
        "organization": dependencies.get("org", "Unknown"),
    }


# Create agent
profile_agent = Agent(
    id="profile-agent",
    name="Profile Agent",
    model=OpenAIResponses(id="gpt-5.2"),
    db=db,
    tools=[get_user_profile],
    instructions="You are a profile agent. You can search for information and access user profiles.",
    add_history_to_context=True,
    markdown=True,
)


app = FastAPI()


# Add a simple endpoint to set the JWT authentication cookie
@app.get("/set-auth-cookie")
async def set_auth_cookie(response: Response):
    """
    Endpoint to set the JWT authentication cookie.
    In a real application, this would be done after successful login.
    """
    # Create a test JWT token
    payload = {
        "sub": "cookie_user_789",
        "session_id": "cookie_session_123",
        "name": "Jane Smith",
        "email": "jane.smith@example.com",
        "roles": ["user", "premium"],
        "org": "Example Corp",
        "exp": datetime.now(UTC) + timedelta(hours=24),
        "iat": datetime.now(UTC),
    }

    token = jwt.encode(payload, JWT_SECRET, algorithm="HS256")

    # Set HTTP-only cookie (more secure than localStorage for JWT storage)
    response.set_cookie(
        key="auth_token",
        value=token,
        httponly=True,  # Prevents JavaScript from reading the cookie
        secure=True,  # Only send over HTTPS in production
        samesite="strict",  # Restricts cross-site cookie sending
        max_age=24 * 60 * 60,  # 24 hours
    )

    return {
        "message": "Authentication cookie set successfully",
        "cookie_name": "auth_token",
        "expires_in": "24 hours",
        "security_features": ["httponly", "secure", "samesite=strict"],
        "instructions": "Now you can make authenticated requests without Authorization headers",
    }


# Add a simple endpoint to clear the JWT authentication cookie
@app.get("/clear-auth-cookie")
async def clear_auth_cookie(response: Response):
    """Endpoint to clear the JWT authentication cookie (logout)."""
    response.delete_cookie(key="auth_token")
    return {"message": "Authentication cookie cleared successfully"}


# Add auth middleware configured for cookie-based authentication
app.add_middleware(
    AuthMiddleware,
    verification_keys=[JWT_SECRET], # or use JWT_VERIFICATION_KEY environment variable
    algorithm="HS256",
    excluded_route_paths=[
        "/set-auth-cookie",
        "/clear-auth-cookie",
    ],
    token_source=TokenSource.COOKIE,  # Extract JWT from cookies
    cookie_name="auth_token",  # Name of the cookie containing the JWT
    user_id_claim="sub",  # Extract user_id from 'sub' claim
    session_id_claim="session_id",  # Extract session_id from 'session_id' claim
    dependencies_claims=[
        "name",
        "email",
        "roles",
        "org",
    ],  # Additional claims to extract
    validate=True,  # We want to ensure the token is valid
)


agent_os = AgentOS(
    description="JWT Cookie-Based AgentOS",
    agents=[profile_agent],
    base_app=app,
)

# Get the final app
app = agent_os.get_app()


if __name__ == "__main__":
    """
    Run your AgentOS with JWT cookie authentication.
    """

    agent_os.serve(
        app="jwt_cookies:app", port=7777, reload=True
    )

Usage

Set up your virtual environment

uv venv --python 3.12
source .venv/bin/activate

Set Environment Variables

export OPENAI_API_KEY=your_openai_api_key

Install dependencies

uv pip install -U agno openai pyjwt "fastapi[standard]" uvicorn sqlalchemy pgvector "psycopg[binary]"

Setup PostgreSQL Database

# 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

Run Example

python jwt_cookies.py

Test Cookie Authentication

Step 1: Set the authentication cookie (-c saves it to a cookie jar)

curl --location -c cookies.txt 'http://localhost:7777/set-auth-cookie'

Step 2: Make authenticated requests using the cookie (-b sends it)

curl --location -b cookies.txt 'http://localhost:7777/agents/profile-agent/runs' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'message=What do you know about me?'

Step 3: Test browser-based authentication

  1. Visit http://localhost:7777/set-auth-cookie in your browser
  2. Visit http://localhost:7777/docs to see the API documentation
  3. Use the "Try it out" feature - cookies are automatically included

Step 4: Clear authentication (logout)

curl --location -b cookies.txt -c cookies.txt 'http://localhost:7777/clear-auth-cookie'

How It Works

  1. Cookie Management: Custom endpoints handle setting and clearing authentication cookies
  2. JWT Middleware: Configured to extract tokens from the auth_token cookie
  3. Token Validation: Verifies the HS256 signature and token expiration
  4. Parameter Injection: Tools read the verified profile claims from RunContext.dependencies
  5. Route Exclusion: Cookie management endpoints excluded from authentication
BehaviorHTTP-only cookiesAuthorization headers
JavaScript accessCannot read an HttpOnly cookie; injected scripts can still send requestsDepends on where the token is stored; localStorage is script-readable
Cross-site requestsSameSite restricts cookie sending; review the application's complete CSRF protectionA bearer header must be added explicitly by the client
Browser requestsBrowser attaches matching cookies automaticallyClient code attaches the header
Server verificationVerifies the JWT read from the cookieVerifies the JWT read from the header

See MDN's cookie attribute reference for HttpOnly, Secure, and SameSite behavior.

Developer Resources