Skip to content

Feature: Add OAuth2/JWT Authentication to Agent Memory Server #12

@abrookins

Description

@abrookins

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

  1. Support OAuth2 Bearer Token Authentication:

    • All API endpoints (except /health and /docs) must require a valid OAuth2 JWT access token in the Authorization: Bearer <token> header.
    • The token should be validated for:
      • Signature (using the issuer’s public key/JWKs).
      • Expiry (must not be expired).
      • Audience (aud claim, if configured).
      • (Optional) Scopes/roles for fine-grained access control.
  2. 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.json at a standard path).
  3. Graceful Failure:

    • Unauthorized requests (missing/invalid/expired token) must return 401 Unauthorized with a helpful error message.
    • If the token is present but invalid (wrong signature, malformed, wrong audience, etc.), return 401 Unauthorized with details.
  4. Easy Local Development (DISABLE_AUTH mode):

    • Add a DISABLE_AUTH environment variable. When set to true, all authentication checks are bypassed and requests are treated as authenticated.
    • In local development or CI, devs set DISABLE_AUTH=true and can make API calls without any token.
    • By default, DISABLE_AUTH=false for production and shared environments.
    • In .env.example and README, document this setting for developers.
  5. 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 (or PyJWT), and fastapi.security as dependencies.
  • If not already present, add httpx for 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 scope claims for endpoints that may need it.

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 raises HTTPException(status_code=401) on failure.
    • Add this dependency to your FastAPI app globally (via a custom APIRouter or middleware), or add it to individual endpoints/routers.
    • Add logic to check the DISABLE_AUTH env variable: if set to true, bypass JWT validation and return a “fake” user (e.g., { "sub": "local-dev-user" }).

4. Configuration

  • Read the following settings from env or config:
    • OAUTH2_ISSUER_URL
    • OAUTH2_AUDIENCE
    • (Optional) OAUTH2_JWKS_URL
    • DISABLE_AUTH (default to false)
  • Add DISABLE_AUTH=true to .env.example and explain its usage in developer docs.

5. Exempt Public Endpoints

  • Exclude /health, /docs, /openapi.json from 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=true to verify all endpoints are accessible for local dev/CI.
    • If possible, use a test issuer (Auth0, Cognito) to issue tokens for CI.

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


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_AUTH is 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions