How to Configure OAuth or Integrate with Existing Authentication Systems in Local Deep Research

Local Deep Research (LDR) relies on a session-based authentication model that you can extend to support external OAuth providers like Google, GitHub, or Azure AD by implementing a custom callback endpoint in src/local_deep_research/mcp/server.py that exchanges provider tokens for LDR session cookies.

Local Deep Research uses secure session cookies as the single source of truth for all authenticated web and API traffic. When integrating external identity providers, you bridge the OAuth flow into this existing architecture rather than replacing it, ensuring per-user encrypted databases and rate-limiting continue to function unchanged.

Understanding LDR's Session-Based Architecture

LDR authenticates every request to /api/* endpoints using a session cookie established at login. According to the source code in src/local_deep_research/mcp/server.py, the application uses a SessionManager class to create and validate these sessions, with explicit comments highlighting the requirement for OAuth, rate limiting, and input validation controls at the top of the file.

The standard login flow documented in docs/api-quickstart.md follows three steps: fetch a CSRF token from /auth/csrf-token, submit credentials to /auth/login, and receive a session cookie for subsequent requests. Your OAuth integration must produce the same cookie outcome so that existing research pipelines and encrypted per-user databases require zero modifications.

Prerequisites for OAuth Integration

Before modifying code, register your application with your chosen identity provider to obtain a client ID and client secret. Then expose these credentials to LDR via environment variables following the pattern established in docs/CONFIGURATION.md:

  • LDR_OAUTH_CLIENT_ID
  • LDR_OAUTH_CLIENT_SECRET

LDR reads configuration from the environment at startup using the same mechanism that handles variables like LDR_NEWS_SCHEDULER_ALLOW_API_CONTROL, ensuring your OAuth credentials are available to the auth routes at runtime.

Implementing the OAuth Callback Endpoint

Add the OAuth Route in server.py

Extend src/local_deep_research/mcp/server.py with a new route—typically GET /auth/oauth/callback—that receives the authorization code from your provider. This endpoint must exchange the code for an access token, retrieve the user's profile, and invoke the internal session creation logic.


# Inside src/local_deep_research/mcp/server.py

import os
import httpx
from fastapi import Request, Query
from fastapi.responses import RedirectResponse

@router.get("/auth/oauth/callback")
async def oauth_callback(request: Request, code: str = Query(...)):
    # Exchange code for token with the provider

    token_resp = await httpx.post(
        "https://provider.com/oauth/token",
        data={
            "client_id": os.getenv("LDR_OAUTH_CLIENT_ID"),
            "client_secret": os.getenv("LDR_OAUTH_CLIENT_SECRET"),
            "code": code,
            "grant_type": "authorization_code"
        },
    )
    token_resp.raise_for_status()
    access_token = token_resp.json()["access_token"]
    
    # Fetch user profile from provider

    provider_user = await fetch_provider_profile(access_token)
    
    # Continue with local user mapping and session creation...

Map External Users to Local Records

After retrieving the provider's user profile (email or unique ID), check for an existing entry in LDR's central auth.db. The schema documented in docs/architecture/DATABASE_SCHEMA.md shows that LDR stores only usernames in the auth table and never stores passwords locally. If no matching user exists, create a new row to establish the local identity.

def get_or_create_local_user(email: str):
    # Query auth.db for existing user

    user = db.query("SELECT id FROM auth WHERE username = ?", (email,)).fetchone()
    if not user:
        # Create minimal local record

        cursor = db.execute("INSERT INTO auth (username) VALUES (?)", (email,))
        user_id = cursor.lastrowid
    else:
        user_id = user["id"]
    return user_id

Reuse the existing SessionManager.login_user() or SessionManager.create_session() methods to generate the session object. This guarantees that the encrypted per-user databases and API rate-limiting logic treat OAuth users identically to password-based users.

from local_deep_research.mcp.server import SessionManager

# After mapping the user...

session = SessionManager.create_session(user_id)

response = RedirectResponse(url="/")
response.set_cookie(
    key="session", 
    value=session.id, 
    httponly=True, 
    secure=True, 
    samesite="lax"
)
return response

Securing the Integration

Protect the OAuth callback endpoint against cross-site request forgery by mirroring the CSRF token behavior used in the standard login flow. The endpoint /auth/csrf-token documented in docs/api-quickstart.md generates tokens that should be validated in your OAuth callback or included in the state parameter during the initial OAuth redirect.

Additionally, ensure your OAuth callback validates the state parameter to prevent CSRF attacks and strictly enforces HTTPS for the cookie secure flag, as the security controls noted in server.py require.

Optional: Enabling Stateless API Tokens

For CLI or service-to-service authentication that cannot use cookies, implement an endpoint that exchanges the provider's OAuth access token for a short-lived LDR API token. The test suite in tests/news/test_api_client_patterns_behavior.py demonstrates the expected Bearer token refresh logic and validation patterns you should follow for stateless authentication.

Summary

  • Session cookies are the core requirement: All LDR features expect the session cookie; OAuth integration must produce this same artifact.
  • Extend src/local_deep_research/mcp/server.py: Add /auth/oauth/callback to handle the provider's authorization code exchange.
  • Map to local users: Use the auth.db schema to link external identities to local rows, preserving per-user encryption boundaries.
  • Reuse SessionManager: Call create_session() to ensure OAuth users inherit existing rate-limiting and database isolation features.
  • Protect against CSRF: Validate CSRF tokens or state parameters in the OAuth flow using the existing /auth/csrf-token endpoint.

Frequently Asked Questions

Can I integrate multiple OAuth providers simultaneously?

Yes. You can implement multiple callback endpoints (e.g., /auth/oauth/github/callback and /auth/oauth/google/callback) in server.py, each normalizing the provider's unique user identifier to the local auth.db username field. The session cookie issued is provider-agnostic, so the rest of LDR functions identically regardless of the authentication source.

Where are user credentials stored when using OAuth?

Local Deep Research never stores passwords or OAuth tokens locally. As shown in docs/architecture/DATABASE_SCHEMA.md, the auth table contains only the username. The OAuth access token exists only transiently during the callback exchange, and the persistent authentication mechanism remains the LDR session cookie managed by SessionManager.

How do I protect the OAuth callback from CSRF attacks?

Include a CSRF token in the OAuth state parameter when redirecting to the provider, then validate this token in your /auth/oauth/callback handler. The existing endpoint /auth/csrf-token documented in docs/api-quickstart.md generates tokens using the same secret key as the session manager, ensuring cryptographic consistency across authentication methods.

Does OAuth integration affect per-user encrypted databases?

No. Because OAuth users are mapped to records in auth.db and issued standard session cookies through SessionManager.create_session(), the existing logic that derives SQLCipher encryption keys from the user ID remains unchanged. Each OAuth user receives the same isolated, encrypted database instance that password users receive.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →