How to Replace Open Notebook's Authentication Middleware for Production
Open Notebook currently uses a simple PasswordAuthMiddleware that validates a static password against the X-Password header, but you should replace it with OAuth2/JWT-based authentication before deploying to production.
Open Notebook (lfnovo/open-notebook) is a research assistant API that currently ships with a development-only authentication scheme. While the PasswordAuthMiddleware defined in api/auth.py provides basic protection via a single shared secret, it lacks user granularity, token revocation, and standard security protocols required for production workloads.
Current Authentication Implementation
The PasswordAuthMiddleware Component
In api/auth.py, the PasswordAuthMiddleware class extends FastAPI's BaseHTTPMiddleware. It reads the OPEN_NOTEBOOK_PASSWORD environment variable (falling back to a default if unset) and expects clients to transmit this value in the X-Password request header. The middleware is registered globally in api/main.py via app.add_middleware(PasswordAuthMiddleware, ...).
Whitelisted Routes and Bypass Logic
The middleware automatically skips authentication for a hard-coded list of public paths including /, /health, /docs, /openapi.json, /redoc, /api/auth/status, and /api/config. This allows OpenAPI documentation and health checks to remain accessible without credentials.
Limitations for Production Use
This approach is unsuitable for production because it relies on a single static secret that cannot be revoked per user, offers no role-based access control, and lacks integration with modern identity providers like Auth0, Azure AD, or Keycloak. If the password is compromised, the entire API is exposed without audit trails or granular permission systems.
Production Replacement Strategy
Step 1 – Remove the Global Middleware
Delete the app.add_middleware(PasswordAuthMiddleware, ...) block from api/main.py (approximately lines 73–86). Keep the CORS middleware configuration, but remove the password-based guard to prevent conflicts with the new security layer.
Step 2 – Implement JWT Security Layer
Create a new api/security.py file to handle token creation and validation. Use fastapi.security.HTTPBearer to extract Bearer tokens from the Authorization header and PyJWT to verify signatures against a JWT_SECRET_KEY environment variable.
Step 3 – Protect Routes with Dependencies
Replace global middleware with per-router dependencies. Import get_current_user from your new security module and apply it to protected routers using dependencies=[Depends(get_current_user)]. This allows the /api/auth/login endpoint in api/routers/auth.py to remain public while securing research assistant endpoints.
Step 4 – Update Environment Configuration
Add JWT_SECRET_KEY and ACCESS_TOKEN_EXPIRE_MINUTES to your CONFIGURATION.md and .env files. Store the JWT signing key in a secrets manager rather than version control, and configure rotation policies to maintain security hygiene.
Implementation Code Examples
New Security Module
Create api/security.py with the following content:
# api/security.py
import os
from datetime import datetime, timedelta
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
bearer = HTTPBearer(auto_error=False)
SECRET_KEY = os.getenv("JWT_SECRET_KEY", "change-me-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
def create_access_token(data: dict) -> str:
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(bearer),
):
if credentials is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.PyJWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"},
)
Updating the Main Application
Modify api/main.py to remove the old middleware and apply the new dependency:
# api/main.py (excerpt)
from fastapi import FastAPI, Depends, APIRouter
from contextlib import asynccontextmanager
from api.security import get_current_user
from api.routers import auth, notes
@asynccontextmanager
async def lifespan(app: FastAPI):
# startup/shutdown logic
yield
app = FastAPI(
title="Open Notebook API",
lifespan=lifespan,
)
# Remove this block entirely:
# from api.auth import PasswordAuthMiddleware
# app.add_middleware(PasswordAuthMiddleware, ...)
# Apply authentication to specific routers
protected_router = APIRouter(
prefix="/api/notes",
dependencies=[Depends(get_current_user)],
)
app.include_router(protected_router)
app.include_router(auth.router)
@app.get("/health")
async def health_check():
return {"status": "ok"}
Creating the Login Endpoint
Update api/routers/auth.py to issue JWT tokens instead of validating the static password for session management:
# api/routers/auth.py
from fastapi import APIRouter, HTTPException, status
from api.security import create_access_token
import os
router = APIRouter()
@router.post("/login")
async def login(password: str):
# Validate against your user database or external IdP
if password != os.getenv("OPEN_NOTEBOOK_PASSWORD"):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect password",
)
token = create_access_token({"sub": "user-id", "role": "researcher"})
return {"access_token": token, "token_type": "bearer"}
Configuration Changes
Update CONFIGURATION.md to document the new variables:
- JWT_SECRET_KEY: RSA private key or HMAC secret for signing tokens. Store in a secrets manager.
- ACCESS_TOKEN_EXPIRE_MINUTES: Token lifetime (default: 60).
- OPEN_NOTEBOOK_PASSWORD: Deprecated for API access; use only for initial admin bootstrap if needed.
Summary
- Open Notebook's default
PasswordAuthMiddlewareinapi/auth.pyuses a single static password fromOPEN_NOTEBOOK_PASSWORDand theX-Passwordheader, which is insecure for production. - Remove the middleware registration from
api/main.pyand implement a JWT-based scheme in a newapi/security.pymodule. - Protect routes using FastAPI's
Depends(get_current_user)rather than global middleware to enable granular access control and proper OpenAPI documentation. - Store
JWT_SECRET_KEYin environment variables or secrets managers and document the changes inCONFIGURATION.md.
Frequently Asked Questions
What authentication middleware does Open Notebook use by default?
The default middleware is PasswordAuthMiddleware, defined in api/auth.py. It validates a static password provided via the X-Password header against the OPEN_NOTEBOOK_PASSWORD environment variable and skips authentication for whitelisted paths like /docs and /health.
Why is the password-based middleware not suitable for production?
It relies on a single shared secret that cannot be revoked per user, lacks role-based access control, and does not support standard protocols like OAuth2 or OpenID Connect. If the password is leaked, the entire API is compromised without audit trails.
Can I replace the middleware with API keys instead of JWT?
Yes. You can modify api/security.py to validate API keys against a database or secure store instead of decoding JWTs. Use fastapi.security.APIKeyHeader to extract the key from headers and replace the get_current_user dependency with a key-validation function.
How do I migrate existing clients to the new authentication method?
Update client applications to request a token from /api/auth/login (using the existing password temporarily) and then include that token in subsequent requests via the Authorization: Bearer <token> header. Gradually deprecate the X-Password header once all clients have migrated.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →