> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Isolate store data per user

> Scope long-term store data to each authenticated user in LangSmith Deployment using auth handlers.

Every [LangSmith Deployment](/langsmith/deployment) includes a Postgres-backed [store](/oss/python/langgraph/stores) for long-term memory and cross-thread data. By default, store namespaces are shared across all callers. To give each user their own isolated store, configure [custom authentication](/langsmith/custom-auth) and add a store [authorization handler](/langsmith/auth#authorization) that rewrites namespaces to include the authenticated user's identity.

This guide shows how to configure that isolation in LangSmith Deployment. The same pattern works for [self-hosted](/langsmith/self-hosted) deployments with custom auth enabled.

## How it works

Store items are organized by a **namespace** tuple (for example, `("memories", "preferences")`) and a **key**. Unlike threads and assistants, store namespaces are defined by your application, so authorization works differently:

1. Your `@auth.authenticate` handler validates the caller and returns a unique `identity` for each user.
2. Your `@auth.on.store` handler either **validates** that the namespace starts with that identity, or **rewrites** the namespace to prepend it.
3. The server uses the validated or rewritten namespace for the actual read or write.

A user who writes to logical namespace `("memories",)` with automatic prefix rewriting actually stores data at `("user-123", "memories")`. Another user cannot read or overwrite that data because their requests are scoped to `("user-456", ...)`.

<Note>
  Store authorization handlers receive a **mutable** `value` dict. Changes to `value["namespace"]` take effect on the operation. You do not return a metadata filter like you do for threads.
</Note>

## Prerequisites

* A LangSmith Deployment with [custom authentication](/langsmith/set-up-custom-auth) configured.
* An `@auth.authenticate` handler that returns a stable, unique `identity` for each end user.

## Configure auth in LangSmith Deployment

Point your deployment at your auth module in [`langgraph.json`](/langsmith/application-structure#configuration-file-concepts):

```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent/graph.py:graph"
  },
  "auth": {
    "path": "./src/security/auth.py:auth"
  }
}
```

The `auth` object must expose an instance of `langgraph_sdk.Auth` (commonly named `auth`).

## Choose a store authorization pattern

Store isolation requires a `@auth.on.store` handler. Two common patterns work well; pick one based on where you want user scoping to live.

| Pattern                        | Where user scope is set                                         | Best when                                                                                               |
| ------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Explicit namespace + deny**  | Application code puts `user_id` first in every namespace        | You already pass user identity into graph code, or you want namespaces to reflect the full storage path |
| **Automatic prefix rewriting** | Auth handler prepends `ctx.user.identity` to logical namespaces | You want simpler agent code and transparent isolation at the API layer                                  |

Both patterns use a single `@auth.on.store` handler that covers all store actions (`put`, `get`, `search`, `delete`, and `list_namespaces`). You only need action-specific handlers such as `@auth.on.store.put` if you want different rules per operation.

<Tip>
  If you use a global `@auth.on` handler that denies unhandled requests, register `@auth.on.store` explicitly so store operations are allowed. See the [Auth reference](https://reference.langchain.com/python/langgraph-sdk/auth/Auth) for action-specific handler names.
</Tip>

### Explicit namespace + deny

Your application code includes the user's identity as the first segment of every namespace (for example, `("user-123", "memories")`). The auth handler validates that the caller matches that prefix and rejects mismatches:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langgraph_sdk import Auth

auth = Auth()

# ... your @auth.authenticate handler ...

@auth.on.store
async def authorize_store(
    ctx: Auth.types.AuthContext,
    value: Auth.types.on.store.value,
):
    """Require the user identity as the first namespace segment."""
    namespace = tuple(value["namespace"])
    if not namespace or namespace[0] != ctx.user.identity:
        raise Auth.exceptions.HTTPException(
            status_code=403,
            detail="Not authorized to access this namespace.",
        )
```

This pattern makes the storage layout explicit in your graph code. A request for namespace `("user-456", "memories")` fails immediately if the authenticated user is `user-123`. The tradeoff is that every store call in your agent must include the user prefix, typically from runtime context such as `config["configurable"]["langgraph_auth_user_id"]`.

### Automatic prefix rewriting

Your application code uses logical namespaces without a user prefix (for example, `("memories", "preferences")`). The auth handler prepends the authenticated user's identity on every store operation:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langgraph_sdk import Auth

auth = Auth()

# ... your @auth.authenticate handler ...

@auth.on.store
async def scope_store(
    ctx: Auth.types.AuthContext,
    value: Auth.types.on.store.value,
):
    """Isolate store data per user by rewriting namespaces."""
    namespace = tuple(value["namespace"]) if value.get("namespace") else ()
    if not namespace or namespace[0] != ctx.user.identity:
        namespace = (ctx.user.identity, *namespace)
    value["namespace"] = namespace
```

When a client calls `list_namespaces` without a prefix, `value["namespace"]` is empty. The handler above treats an empty namespace as `(ctx.user.identity,)`, so the user only sees their own namespaces.

This pattern keeps agent code simpler because the auth layer handles user isolation transparently. The tradeoff is that you must not also prefix namespaces in application code, or you will double-scope data and break reads. Pick one scoping layer: auth rewriting **or** application-level namespaces, not both.

## Use namespaces in agent code

If you use automatic prefix rewriting, your graph code uses logical namespaces without the user prefix. The auth layer adds the user scope automatically at request time. Put the following in your graph nodes or tools (for example, `graph.py`):

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langgraph.store.base import BaseStore

async def save_preference(state: State, *, store: BaseStore):
    await store.aput(
        ("memories", "preferences"),  # logical namespace
        "settings",
        {"theme": "dark"},
    )

async def load_preference(state: State, *, store: BaseStore):
    item = await store.aget(
        ("memories", "preferences"),
        "settings",
    )
    return item.value if item else {}
```

When User A calls the store API, the server persists data at `("user-a", "memories", "preferences")`. User B's requests never reach that namespace.

If you use **explicit namespace + deny**, include the user identity in every namespace from your graph code instead:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langchain_core.runnables import RunnableConfig
from langgraph.store.base import BaseStore

async def save_preference(state: State, *, store: BaseStore, config: RunnableConfig):
    user_id = config["configurable"]["langgraph_auth_user_id"]
    await store.aput(
        (user_id, "memories", "preferences"),
        "settings",
        {"theme": "dark"},
    )
```

### Deep Agents and StoreBackend

If you use [Deep Agents](/oss/python/deepagents/overview) with a [`StoreBackend`](https://reference.langchain.com/python/deepagents/backends/store/StoreBackend), align your namespace factory with the auth-scoped layout. When auth prepends `ctx.user.identity`, use logical namespaces in the backend and let auth handle user isolation:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from deepagents.backends import StoreBackend

StoreBackend(
    namespace=lambda rt: ("memories",),  # auth adds user identity
)
```

Alternatively, if you scope by user inside the graph (for example, with `rt.server_info.user.identity`), ensure your auth handler does not double-prefix namespaces. Pick one scoping layer: auth rewriting **or** application-level namespaces, not both.

For more on namespace design, see [Deep Agents backends](/oss/python/deepagents/backends#namespace-factories) and [user-scoped memory](/oss/python/deepagents/memory#user-scoped-memory).

## Test store isolation

With `langgraph dev` running and two test users configured in your auth handler, verify that store data does not leak across users:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import asyncio

from langgraph_sdk import get_client


async def main():
    alice = get_client(
        url="http://localhost:60058",
        headers={"Authorization": "Bearer user1-token"},
    )
    bob = get_client(
        url="http://localhost:60058",
        headers={"Authorization": "Bearer user2-token"},
    )

    # Alice writes a store item
    await alice.store.put_item(
        ["memories"],
        key="note",
        value={"text": "Alice private note"},
    )

    # Bob cannot read Alice's item
    bob_item = await bob.store.get_item(["memories"], key="note")
    if bob_item is None or bob_item["value"]["text"] != "Alice private note":
        print("✅ Bob correctly denied access to Alice's item")
    else:
        print("❌ Bob should not see Alice's store item")

    # Bob writes his own item at the same logical namespace
    await bob.store.put_item(
        ["memories"],
        key="note",
        value={"text": "Bob private note"},
    )

    alice_item = await alice.store.get_item(["memories"], key="note")
    bob_item = await bob.store.get_item(["memories"], key="note")
    assert alice_item["value"]["text"] == "Alice private note"
    assert bob_item["value"]["text"] == "Bob private note"
    print("✅ Each user sees only their own store data")


if __name__ == "__main__":
    asyncio.run(main())
```

## Combine with thread isolation

Store isolation and [thread isolation](/langsmith/resource-auth) solve different problems:

| Concern              | Mechanism                         | Scopes                                            |
| -------------------- | --------------------------------- | ------------------------------------------------- |
| Conversation history | Thread metadata filters (`owner`) | Per-thread checkpoints and messages               |
| Long-term memory     | Store namespace rewriting         | Cross-thread memories, preferences, and documents |

For multi-user agents, configure both. See [Make conversations private](/langsmith/resource-auth) for thread and run scoping.

## See also

* [Authentication and access control](/langsmith/auth)
* [Set up custom authentication](/langsmith/set-up-custom-auth)
* [Make conversations private](/langsmith/resource-auth)
* [Semantic search in deployments](/langsmith/semantic-search)
* [LangGraph stores](/oss/python/langgraph/stores)

***

<div className="source-links">
  <Callout icon="terminal-2">
    [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
  </Callout>

  <Callout icon="edit">
    [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/store-auth.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>
</div>
