# How to Replace Open Notebook's Authentication Middleware for Production

> Replace Open Notebook's basic password authentication with secure OAuth2 JWT for production. Learn how to implement robust security for your application.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-19

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main//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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/api/security.py) with the following content:

```python

# 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`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) to remove the old middleware and apply the new dependency:

```python

# 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`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/auth.py) to issue JWT tokens instead of validating the static password for session management:

```python

# 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`](https://github.com/lfnovo/open-notebook/blob/main/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 `PasswordAuthMiddleware` in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) uses a single static password from `OPEN_NOTEBOOK_PASSWORD` and the `X-Password` header, which is insecure for production.
- Remove the middleware registration from [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) and implement a JWT-based scheme in a new [`api/security.py`](https://github.com/lfnovo/open-notebook/blob/main/api/security.py) module.
- 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_KEY` in environment variables or secrets managers and document the changes in [`CONFIGURATION.md`](https://github.com/lfnovo/open-notebook/blob/main/CONFIGURATION.md).

## Frequently Asked Questions

### What authentication middleware does Open Notebook use by default?

The default middleware is `PasswordAuthMiddleware`, defined in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.