nlweb-auth-multitenancy

Configure NLWeb authentication and multi-tenant deployments — OAuth providers (GitHub, Google, Microsoft, Facebook), session storage, the `sites:` allowlist in `config_nlweb.yaml`, conversation persistence per authenticated user, and per-tenant data isolation. Use when adding login to an NLWeb instance, hosting multiple customers on one deployment, or persisting conversation history.

17 stars

Best use case

nlweb-auth-multitenancy is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Configure NLWeb authentication and multi-tenant deployments — OAuth providers (GitHub, Google, Microsoft, Facebook), session storage, the `sites:` allowlist in `config_nlweb.yaml`, conversation persistence per authenticated user, and per-tenant data isolation. Use when adding login to an NLWeb instance, hosting multiple customers on one deployment, or persisting conversation history.

Teams using nlweb-auth-multitenancy should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/nlweb-auth-multitenancy/SKILL.md --create-dirs "https://raw.githubusercontent.com/OrcaQubits/agentic-commerce-skills-plugins/main/dist/antigravity/nlweb-protocol/.agent/skills/nlweb-auth-multitenancy/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/nlweb-auth-multitenancy/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How nlweb-auth-multitenancy Compares

Feature / Agentnlweb-auth-multitenancyStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Configure NLWeb authentication and multi-tenant deployments — OAuth providers (GitHub, Google, Microsoft, Facebook), session storage, the `sites:` allowlist in `config_nlweb.yaml`, conversation persistence per authenticated user, and per-tenant data isolation. Use when adding login to an NLWeb instance, hosting multiple customers on one deployment, or persisting conversation history.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# NLWeb Auth & Multitenancy

## Before writing code

**Fetch live docs**:
1. Fetch https://github.com/nlweb-ai/NLWeb/blob/main/docs/setup-oauth.md for OAuth configuration.
2. Fetch https://github.com/nlweb-ai/NLWeb/blob/main/docs/nlweb-memory.md for conversation persistence.
3. Inspect `AskAgent/python/webserver/routes/oauth.py` for the current OAuth flow.
4. Inspect `AskAgent/python/core/conversation_history.py` and `storage_providers/` for persistence backends.
5. Check `config/config_oauth.yaml` and `config/config_storage.yaml` for current keys.

## Conceptual Architecture

### NLWeb's Auth Model — What It Does and Doesn't Do

NLWeb ships OAuth-based **user identification** — it lets a logged-in user have persistent conversation memory tied to their identity. It does **not** ship:
- Fine-grained authorization (per-site ACLs)
- API key auth for service-to-service callers
- Multi-tenant data isolation at the retrieval layer

If you need any of those, you build them as middleware on top.

### OAuth Providers Supported

Per `config_oauth.yaml`:

| Provider | Notes |
|----------|-------|
| GitHub | Standard OAuth 2.0 |
| Google | Standard OAuth 2.0 |
| Microsoft | Entra ID / personal accounts |
| Facebook | Standard OAuth 2.0 |

Adding a new provider means a new client class in the OAuth routes module + a config entry. Verify the current extensibility mechanism in the live code.

### OAuth Routes

| Route | Purpose |
|-------|---------|
| `GET /api/oauth/login/{provider}` | Start the OAuth dance |
| `GET /api/oauth/callback/{provider}` | OAuth callback handler |
| `GET /api/oauth/logout` | End session |
| `GET /api/oauth/me` | Current user info |

(Verify exact paths in `webserver/routes/oauth.py`.)

### Session Storage

By default, NLWeb stores sessions in-memory or via an aiohttp session backend. For multi-instance deployments, configure a shared session store (Redis, etc.). The session cookie carries the user identity; conversation persistence keys off that identity.

### Conversation Persistence

`config_storage.yaml` selects which storage backend persists conversations:

| Backend | Notes |
|---------|-------|
| Qdrant (`qdrant_storage.py`) | Conversations as vectors — enables `conversation_search` tool |
| Azure AI Search (`azure_search_storage.py`) | Same idea, on Azure |
| Elasticsearch (`elasticsearch_storage.py`) | Same idea, on ES |

The choice often matches your retrieval backend so conversation search and content search share infrastructure. Anonymous users typically don't get persistence — verify if/how the config exposes this toggle.

### Multitenancy via `sites:` Allowlist

`config_nlweb.yaml` has a `sites:` list of allowed site names. Queries with `site=` not in the list are rejected. Patterns:

**Single-tenant**: just enumerate your own sites.

**Multi-customer SaaS**: prefix every site with a tenant ID (`tenant_a__products`, `tenant_b__products`), and add middleware that:
1. Reads the authenticated user's tenant from the session
2. Rewrites incoming `site` params to scope to that tenant's sites only
3. Rejects queries asking for sites outside the tenant's scope

NLWeb does not ship this middleware — you write it.

### Per-Tenant Data Isolation

At the retrieval layer:
- **Cheap path**: site naming convention as above. Single index, queries filter by site. Cheap but tenants share an index.
- **Strong isolation**: separate retrieval indexes / collections / databases per tenant. Configure NLWeb with multiple endpoints (e.g., `qdrant_tenant_a`, `qdrant_tenant_b`) and route based on the authenticated user.

The strong-isolation path requires more config wrangling but is the only safe choice for regulated tenants.

### User Identity in Conversation Search

`methods/conversation_search.py` queries the conversation storage scoped to the current user. The user ID flows from the OAuth session into the handler context. Without OAuth, this tool returns empty.

### Headers for Permission Signaling

NLWeb's in-stream "headers" (the `message_type` JSON objects in SSE) include `usage_terms` and `rate_limits`. These can carry per-user policy — e.g., a higher-tier user gets a higher `rate_limits.daily_quota`. NLWeb doesn't enforce this; the client agent inspects and respects it.

## Implementation Guidance

### Enabling OAuth

1. Register an OAuth app with the provider (e.g., GitHub OAuth Apps).
2. Set the redirect URI to `https://your-host/api/oauth/callback/github`.
3. Set env vars (verify exact names in `config_oauth.yaml`):
   ```
   GITHUB_OAUTH_CLIENT_ID=...
   GITHUB_OAUTH_CLIENT_SECRET=...
   ```
4. Edit `config_oauth.yaml` to enable the provider:
   ```yaml
   providers:
     github:
       enabled: true
       scopes: ["read:user"]
   ```
5. Restart. Visit `/api/oauth/login/github` to test.

### Adding Multi-Tenant Middleware

A sketch (aiohttp middleware):

```python
@web.middleware
async def tenant_scope_middleware(request, handler):
    user = await get_user_from_session(request)
    if user is None:
        return web.json_response({"error": "auth required"}, status=401)

    requested_site = request.query.get("site") or ""
    allowed_prefix = f"{user['tenant_id']}__"
    if not requested_site.startswith(allowed_prefix):
        return web.json_response({"error": "site not in tenant scope"}, status=403)

    return await handler(request)
```

Register on the aiohttp app before NLWeb's own handlers. Verify the exact insertion point in `webserver/aiohttp_server.py`.

### Persisting Conversations Per User

1. Pick a storage backend in `config_storage.yaml` (Qdrant for dev, Azure Search / ES for prod).
2. Ensure OAuth is on (anonymous users don't get persistence by default).
3. Verify the storage class records `user_id` on each conversation row — it does in current code; verify after upgrade.

### Letting Anonymous Users Query Without Persistence

For public sites:
- Leave OAuth optional
- Allow anonymous `/ask` but skip persistence
- Disable `conversation_search` tool for anonymous users (it would return empty anyway)

Confirm the current behavior — anonymous policy has changed across releases.

### API Keys for Service Callers

NLWeb does NOT ship API key auth out of the box. Add it as middleware:

```python
@web.middleware
async def api_key_middleware(request, handler):
    key = request.headers.get("X-API-Key")
    if request.path.startswith("/api/oauth/"):
        return await handler(request)  # OAuth flow exempt
    if not is_valid_key(key):
        return web.json_response({"error": "invalid key"}, status=401)
    return await handler(request)
```

Issue keys via a separate admin endpoint or out-of-band.

### Hardening Sessions for Multi-Instance

- Use a shared session backend (Redis via `aiohttp-session` redis storage).
- Set secure cookie flags (`Secure`, `HttpOnly`, `SameSite=Lax`).
- Rotate the session secret on a schedule.
- Set a sane session TTL.

### Common Pitfalls

- **OAuth callback URL mismatch** — the provider rejects the redirect. Copy the URL byte-for-byte.
- **In-memory sessions lose users on restart** — wire a shared backend before going multi-instance.
- **Conversations not persisting** — storage backend not configured OR user is anonymous OR storage backend's index doesn't exist.
- **Tenant leakage** — middleware order is wrong, OR the storage backend isn't filtering by user. Pen-test before launch.
- **`who` endpoint exposes tenant names** — disable `who_endpoint_enabled` for multitenant deployments.

Always re-fetch the OAuth and storage docs from the live repo — auth code moves between releases.

Related Skills

webmcp-authentication

17
from OrcaQubits/agentic-commerce-skills-plugins

Implement WebMCP authentication patterns — browser session inheritance, cookie-based auth, role-gated tool registration, and conditional tool exposure. Use when managing which tools are available based on user authentication state.

ucp-schema-authoring

17
from OrcaQubits/agentic-commerce-skills-plugins

Author custom UCP schemas and extensions — create capability schemas, extension schemas, and type definitions using JSON Schema 2020-12 composition. Use when extending UCP with custom capabilities or building domain-specific extensions.

nlweb-tools-framework

17
from OrcaQubits/agentic-commerce-skills-plugins

Design and implement NLWeb tools — the per-Schema.org-type handlers that turn a query into a specialized response (search, item_details, compare_items, ensemble, recipe_substitution, accompaniment, conversation_search, etc.). Covers `tools.xml`, the ToolSelector router, builtin handlers in `methods/`, writing a custom tool with a `<returnStruc>` contract, and disabling tool selection for raw retrieval. Use when extending NLWeb beyond the default query → results flow.

nlweb-setup

17
from OrcaQubits/agentic-commerce-skills-plugins

Bootstrap a local NLWeb development environment from scratch — clone the repo, configure .env, install Python deps via `nlweb init-python`, run `nlweb init` for interactive LLM/retrieval selection, load sample Schema.org data, and verify with `nlweb check`. Use when starting a new NLWeb deployment from zero.

nlweb-schema-org-grounding

17
from OrcaQubits/agentic-commerce-skills-plugins

Prepare and structure site content as Schema.org JSON-LD for NLWeb ingestion — covers the supported types (Recipe, Product, Movie, Event, Article, RealEstate, Course, etc.), per-type behavior in NLWeb's tool routing, JSON-LD embedding patterns in HTML, sites.xml registration, and how the `schema_object` flows through ranking back to agent results. Use when authoring or auditing the structured data on a site that will be exposed via NLWeb.

nlweb-retrieval-backends

17
from OrcaQubits/agentic-commerce-skills-plugins

Choose and configure NLWeb retrieval backends — Qdrant (local + remote), Azure AI Search, Elasticsearch, OpenSearch (with/without k-NN), Postgres pgvector, Milvus, Snowflake Cortex Search, Cloudflare AutoRAG, Shopify MCP, and Bing Web Search. Covers `config_retrieval.yaml`, the single `write_endpoint` rule, parallel read-fanout with URL dedup, and per-backend setup pages. Use when picking a retrieval store, migrating between backends, or debugging "results are empty."

nlweb-prompts-customization

17
from OrcaQubits/agentic-commerce-skills-plugins

Customize NLWeb's LLM prompts and per-Schema.org-type behavior via `prompts.xml` and `site_types.xml` — covers the `<promptString>` template format, `<returnStruc>` JSON schemas, prompt inheritance, decontextualization/ranking/generate templates, per-site overrides, and pitfalls of editing prompts in place. Use when tuning answer quality, supporting a new domain, or localizing prompts.

nlweb-mcp-server

17
from OrcaQubits/agentic-commerce-skills-plugins

Expose NLWeb as an MCP (Model Context Protocol) server — JSON-RPC 2.0 endpoint at /mcp, the `ask` / `list_sites` / `who` tools, MCP protocol version 2024-11-05, and integration with ChatGPT, Claude, Gemini, and other agent clients. Use when wiring NLWeb to an AI agent via MCP or building an MCP client that consumes an NLWeb site.

nlweb-llm-providers

17
from OrcaQubits/agentic-commerce-skills-plugins

Configure NLWeb LLM and embedding providers — OpenAI, Azure OpenAI (default), Anthropic, Google Gemini, DeepSeek on Azure, Llama on Azure, HuggingFace, Inception Labs, Snowflake Cortex, Ollama, Pi Labs. Covers `config_llm.yaml` high/low tier model selection, the ModelRouter cost/quality routing logic, `config_embedding.yaml`, and adding a custom provider. Use when picking models, tuning cost, or wiring a new LLM backend.

nlweb-data-loading

17
from OrcaQubits/agentic-commerce-skills-plugins

Ingest site content into NLWeb's vector store using `db_load.py` — supports RSS/Atom feeds, Schema.org JSON-LD, sitemap-driven URL lists, and CSV. Covers chunking, embedding computation, site partitioning, batch sizing, delete-and-reload, and per-backend write_endpoint targeting. Use when bootstrapping a site's index, refreshing content, or migrating between retrieval backends.

nlweb-chatgpt-appsdk

17
from OrcaQubits/agentic-commerce-skills-plugins

Integrate NLWeb with ChatGPT's Apps SDK — the Node.js MCP server in `openai-apps-sdk-integration/`, the `nlweb-list` tool, the React widget at `ui://widget/nlweb-list.html`, and the port-8100 AppSDK adapter that translates NLWeb's message list to OpenAI Apps SDK envelopes. Use when publishing an NLWeb site as a ChatGPT app or wiring NLWeb results into an Apps SDK widget.

nlweb-ask-endpoint

17
from OrcaQubits/agentic-commerce-skills-plugins

Implement and consume the NLWeb /ask REST endpoint — request shape (GET/POST, query-string and v0.55 structured body), SSE streaming response, modes (list/summarize/generate), in-stream "message_type" headers, error envelopes, and client-side parsing. Use when building an NLWeb server route, calling /ask from a custom agent, or debugging /ask responses.