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.
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/activateSet Environment Variables
export OPENAI_API_KEY=your_openai_api_keyInstall 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:pg17Run Example
python jwt_cookies.pyTest 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
- Visit http://localhost:7777/set-auth-cookie in your browser
- Visit http://localhost:7777/docs to see the API documentation
- 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
- Cookie Management: Custom endpoints handle setting and clearing authentication cookies
- JWT Middleware: Configured to extract tokens from the
auth_tokencookie - Token Validation: Verifies the HS256 signature and token expiration
- Parameter Injection: Tools read the verified profile claims from
RunContext.dependencies - Route Exclusion: Cookie management endpoints excluded from authentication
Cookie vs Header Authentication
| Behavior | HTTP-only cookies | Authorization headers |
|---|---|---|
| JavaScript access | Cannot read an HttpOnly cookie; injected scripts can still send requests | Depends on where the token is stored; localStorage is script-readable |
| Cross-site requests | SameSite restricts cookie sending; review the application's complete CSRF protection | A bearer header must be added explicitly by the client |
| Browser requests | Browser attaches matching cookies automatically | Client code attaches the header |
| Server verification | Verifies the JWT read from the cookie | Verifies the JWT read from the header |
See MDN's cookie attribute reference for HttpOnly, Secure, and SameSite behavior.