-
Notifications
You must be signed in to change notification settings - Fork 29
Description
Feature: Add OAuth2/JWT Authentication to Agent Memory Server
Summary:
Upgrade the authentication layer in the Agent Memory Server to support OAuth2 using JWT bearer tokens. This enables secure, standards-based authentication for multiple clients and makes it easier to integrate with enterprise identity providers (AWS Cognito, Auth0, Okta, etc.). The goal is to support OAuth2 Client Credentials flow for machine-to-machine/API auth, and validate JWT access tokens on every request.
Requirements
-
Support OAuth2 Bearer Token Authentication:
- All API endpoints (except
/healthand/docs) must require a valid OAuth2 JWT access token in theAuthorization: Bearer <token>header. - The token should be validated for:
- Signature (using the issuer’s public key/JWKs).
- Expiry (must not be expired).
- Audience (
audclaim, if configured). - (Optional) Scopes/roles for fine-grained access control.
- All API endpoints (except
-
Configurable OAuth2 Parameters:
- Allow configuration (via environment variables or config file) of:
OAUTH2_ISSUER_URL(the base URL of the token issuer; e.g., your Cognito or Auth0 domain).OAUTH2_AUDIENCE(expected audience value in the token).- (Optional)
OAUTH2_JWKS_URL(if issuer does not expose.well-known/jwks.jsonat a standard path).
- Allow configuration (via environment variables or config file) of:
-
Graceful Failure:
- Unauthorized requests (missing/invalid/expired token) must return
401 Unauthorizedwith a helpful error message. - If the token is present but invalid (wrong signature, malformed, wrong audience, etc.), return
401 Unauthorizedwith details.
- Unauthorized requests (missing/invalid/expired token) must return
-
Easy Local Development (DISABLE_AUTH mode):
- Add a
DISABLE_AUTHenvironment variable. When set totrue, all authentication checks are bypassed and requests are treated as authenticated. - In local development or CI, devs set
DISABLE_AUTH=trueand can make API calls without any token. - By default,
DISABLE_AUTH=falsefor production and shared environments. - In
.env.exampleand README, document this setting for developers.
- Add a
-
Documentation and Examples:
- Update README to document how to configure OAuth2 and the local development bypass.
- Provide example curl commands for calling the API with a JWT and for using
DISABLE_AUTH=true.
Implementation Steps
1. Dependencies
- Add
python-jose(orPyJWT), andfastapi.securityas dependencies. - If not already present, add
httpxfor fetching JWKS keys if you want to support dynamic key rotation.
2. OAuth2/JWT Validation Utility
- Write a utility function to:
- Parse the
Authorization: Bearer <token>header. - Fetch and cache JWKS public keys from the OAuth2 issuer (cache these for performance).
- Validate the JWT’s signature, expiry, and audience.
- Optionally, validate
scopeclaims for endpoints that may need it.
- Parse the
3. FastAPI Security Dependency
- Use FastAPI’s dependency injection to enforce authentication on endpoints.
- Import and use
from fastapi.security import HTTPBearer. - Write a dependency (
get_current_user) that runs on every request (except public endpoints), validates the JWT, and raisesHTTPException(status_code=401)on failure. - Add this dependency to your FastAPI app globally (via a custom
APIRouteror middleware), or add it to individual endpoints/routers. - Add logic to check the
DISABLE_AUTHenv variable: if set totrue, bypass JWT validation and return a “fake” user (e.g.,{ "sub": "local-dev-user" }).
- Import and use
4. Configuration
- Read the following settings from env or config:
OAUTH2_ISSUER_URLOAUTH2_AUDIENCE- (Optional)
OAUTH2_JWKS_URL DISABLE_AUTH(default tofalse)
- Add
DISABLE_AUTH=trueto.env.exampleand explain its usage in developer docs.
5. Exempt Public Endpoints
- Exclude
/health,/docs,/openapi.jsonfrom auth (these should remain public). - All other endpoints must require a valid bearer token (unless
DISABLE_AUTH=true).
6. Testing
- Add tests that:
- Call protected endpoints without a token (should return 401 unless
DISABLE_AUTH=true). - Call with a valid token (should succeed).
- Call with an invalid/expired/wrong-audience token (should 401).
- Run tests with
DISABLE_AUTH=trueto verify all endpoints are accessible for local dev/CI. - If possible, use a test issuer (Auth0, Cognito) to issue tokens for CI.
- Call protected endpoints without a token (should return 401 unless
7. Documentation
- Update the README:
- Explain which OAuth2 providers are supported and how to configure them.
- Document all relevant env vars, especially
DISABLE_AUTH. - Provide example curl/Python code to authenticate (with and without auth).
- Mention any migration advice for current users (e.g., API key auth is now deprecated).
Helpful Resources
-
FastAPI Security:
https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/ -
Example implementation (with PyJWT):
https://github.com/tiangolo/fastapi/blob/master/docs/tutorial/security/oauth2-jwt.md -
AWS Cognito JWT validation reference:
https://aws.amazon.com/blogs/security/best-practices-for-validating-user-pool-jwts/
Please feel free to ask for clarifications on FastAPI security, JWT structure, or integration with specific identity providers.
Example Code Snippet (for dependency):
import os
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import jwt
oauth2_scheme = HTTPBearer()
def get_public_key():
# Fetch and cache JWKS keys from the issuer
# Implementation depends on your IDP; see README for examples
...
def verify_jwt(token: str):
public_key = get_public_key()
# Validate signature, expiry, audience, etc.
try:
payload = jwt.decode(
token,
public_key,
audience=os.environ["OAUTH2_AUDIENCE"],
issuer=os.environ["OAUTH2_ISSUER_URL"],
algorithms=["RS256"],
)
return payload
except Exception as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Invalid JWT: {str(e)}",
)
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(oauth2_scheme)):
if os.getenv("DISABLE_AUTH", "false").lower() == "true":
# In local/dev, skip auth and return dummy claims
return {"sub": "local-dev-user", "roles": ["admin"]}
return verify_jwt(credentials.credentials)Apply Depends(get_current_user) to routers or the app (except /health, /docs, etc.).
Acceptance Criteria
- All API endpoints (except health/docs) require a valid JWT bearer token unless
DISABLE_AUTH=true. - Tokens are validated for signature, expiry, and audience.
- Configurable issuer/audience/JWKS via env or config.
-
DISABLE_AUTHis documented and works for local dev/CI. - Unauthorized access returns 401 with details.
- README documents setup and usage for both production and local development.
- Tests included for both auth and local dev modes.