diff --git a/docs/docs/concepts/auth.md b/docs/docs/concepts/auth.md index 142c64124..d5119e9e4 100644 --- a/docs/docs/concepts/auth.md +++ b/docs/docs/concepts/auth.md @@ -17,6 +17,22 @@ While often used interchangeably, these terms represent distinct security concep In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers. +## Default Security Models + +LangGraph Platform provides different security defaults: + +### LangGraph Cloud + +- Uses LangSmith API keys by default +- Requires valid API key in `x-api-key` header +- Can be customized with your auth handler + +### Self-Hosted + +- No default authentication +- Complete flexibility to implement your security model +- You control all aspects of authentication and authorization + ## System Architecture A typical authentication setup involves three main components: @@ -123,7 +139,7 @@ The returned user information is available: After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can: -1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. +1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](##supported-actions) for the list of types the value can take for each action. 2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations). 3. Raise an HTTP exception if access is denied. @@ -342,10 +358,6 @@ async def rbac_create(ctx: Auth.types.AuthContext, value: dict): ## Supported Resources -LangGraph provides authorization handlers for the following resource types: - -## Supported Resources - LangGraph provides three levels of authorization handlers, from most general to most specific: 1. **Global Handler** (`@auth.on`): Matches all resources and actions @@ -381,46 +393,32 @@ If a more specific handler is registered, the more general handler will not be c ``` More specific handlers provide better type hints since they handle fewer action types. +#### Supported actions and types {#supported-actions} Here are all the supported action handlers: -| Resource | Handler | Description | -|----------|---------|-------------| -| **Threads** | `@auth.on.threads.create` | Thread creation | -| | `@auth.on.threads.read` | Thread retrieval | -| | `@auth.on.threads.update` | Thread updates | -| | `@auth.on.threads.delete` | Thread deletion | -| | `@auth.on.threads.search` | Listing threads | -| | `@auth.on.threads.create_run` | Creating or updating a run | -| **Assistants** | `@auth.on.assistants.create` | Assistant creation | -| | `@auth.on.assistants.read` | Assistant retrieval | -| | `@auth.on.assistants.update` | Assistant updates | -| | `@auth.on.assistants.delete` | Assistant deletion | -| | `@auth.on.assistants.search` | Listing assistants | -| **Crons** | `@auth.on.crons.create` | Cron job creation | -| | `@auth.on.crons.read` | Cron job retrieval | -| | `@auth.on.crons.update` | Cron job updates | -| | `@auth.on.crons.delete` | Cron job deletion | -| | `@auth.on.crons.search` | Listing cron jobs | +| Resource | Handler | Description | Value Type | +|----------|---------|-------------|------------| +| **Threads** | `@auth.on.threads.create` | Thread creation | [`ThreadsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsCreate) | +| | `@auth.on.threads.read` | Thread retrieval | [`ThreadsRead`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsRead) | +| | `@auth.on.threads.update` | Thread updates | [`ThreadsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsUpdate) | +| | `@auth.on.threads.delete` | Thread deletion | [`ThreadsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsDelete) | +| | `@auth.on.threads.search` | Listing threads | [`ThreadsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsSearch) | +| | `@auth.on.threads.create_run` | Creating or updating a run | [`RunsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.RunsCreate) | +| **Assistants** | `@auth.on.assistants.create` | Assistant creation | [`AssistantsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsCreate) | +| | `@auth.on.assistants.read` | Assistant retrieval | [`AssistantsRead`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsRead) | +| | `@auth.on.assistants.update` | Assistant updates | [`AssistantsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsUpdate) | +| | `@auth.on.assistants.delete` | Assistant deletion | [`AssistantsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsDelete) | +| | `@auth.on.assistants.search` | Listing assistants | [`AssistantsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsSearch) | +| **Crons** | `@auth.on.crons.create` | Cron job creation | [`CronsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsCreate) | +| | `@auth.on.crons.read` | Cron job retrieval | [`CronsRead`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsRead) | +| | `@auth.on.crons.update` | Cron job updates | [`CronsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsUpdate) | +| | `@auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsDelete) | +| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsSearch) | ???+ note "About Runs" Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers. There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler. -## Default Security Models - -LangGraph Platform provides different security defaults: - -### LangGraph Cloud - -- Uses LangSmith API keys by default -- Requires valid API key in `x-api-key` header -- Can be customized with your auth handler - -### Self-Hosted - -- No default authentication -- Complete flexibility to implement your security model -- You control all aspects of authentication and authorization ## Next Steps diff --git a/docs/docs/tutorials/auth/getting_started.md b/docs/docs/tutorials/auth/getting_started.md index 0f8a79bae..02fce1c58 100644 --- a/docs/docs/tutorials/auth/getting_started.md +++ b/docs/docs/tutorials/auth/getting_started.md @@ -5,6 +5,7 @@ 1. Basic Authentication (you are here) - Control who can access your bot 2. [Resource Authorization](resource_auth.md) - Let users have private conversations 3. [Production Auth](add_auth_server.md) - Add real user accounts and validate using OAuth2 + !!! tip "Prerequisites" This guide assumes basic familiarity with the following concepts: @@ -46,8 +47,12 @@ The graph should run, and if you were to self-host this on the public internet, ![No auth](./img/no_auth.png) -Now that we've seen the base LangGraph app, let's add authentication to it! In part 1, we will start with a hard-coded token for illustration purposes. -We will get to a "production-ready" authentication scheme in part 3, after mastering the basics. +Now that we've seen the base LangGraph app, let's add authentication to it! + +???+ tip "Placeholder token" + + In part 1, we will start with a hard-coded token for illustration purposes. + We will get to a "production-ready" authentication scheme in part 3, after mastering the basics. ## Adding Authentication diff --git a/docs/docs/tutorials/auth/resource_auth.md b/docs/docs/tutorials/auth/resource_auth.md index e62b88f60..46494e4ff 100644 --- a/docs/docs/tutorials/auth/resource_auth.md +++ b/docs/docs/tutorials/auth/resource_auth.md @@ -10,6 +10,11 @@ In this tutorial, we will extend our chatbot to give each user their own private ![Authorization handlers](./img/authorization.png) +???+ tip "Placeholder token" + + As we did in [part 1](getting_started.md), for this section, we will use a hard-coded token for illustration purposes. + We will get to a "production-ready" authentication scheme in part 3, after mastering the basics. + ## Understanding Resource Authorization In the last tutorial, we controlled who could access our bot. But right now, any authenticated user can see everyone else's conversations! Let's fix that by adding [resource authorization](../../concepts/auth.md#resource-authorization). @@ -68,7 +73,51 @@ async def add_owner( value: dict, # The resource being created/accessed ): """Make resources private to their creator.""" - # Add owner when creating resources + # Examples: + # ctx: AuthContext( + # permissions=[], + # user=ProxyUser( + # identity='user1', + # is_authenticated=True, + # display_name='user1' + # ), + # resource='threads', + # action='create_run' + # ) + # value: + # { + # 'thread_id': UUID('1e1b2733-303f-4dcd-9620-02d370287d72'), + # 'assistant_id': UUID('fe096781-5601-53d2-b2f6-0d3403f7e9ca'), + # 'run_id': UUID('1efbe268-1627-66d4-aa8d-b956b0f02a41'), + # 'status': 'pending', + # 'metadata': {}, + # 'prevent_insert_if_inflight': True, + # 'multitask_strategy': 'reject', + # 'if_not_exists': 'reject', + # 'after_seconds': 0, + # 'kwargs': { + # 'input': {'messages': [{'role': 'user', 'content': 'Hello!'}]}, + # 'command': None, + # 'config': { + # 'configurable': { + # 'langgraph_auth_user': ... Your user object... + # 'langgraph_auth_user_id': 'user1' + # } + # }, + # 'stream_mode': ['values'], + # 'interrupt_before': None, + # 'interrupt_after': None, + # 'webhook': None, + # 'feedback_keys': None, + # 'temporary': False, + # 'subgraphs': False + # } + # } + + # Do 2 things: + # 1. Add the user's ID to the resource's metadata. Each LangGraph resource has a `metadata` dict that persists with the resource. + # this metadata is useful for filtering in read and update operations + # 2. Return a filter that lets users only see their own resources filters = {"owner": ctx.user.identity} metadata = value.setdefault("metadata", {}) metadata.update(filters) @@ -182,11 +231,15 @@ async def on_thread_create( 1. Sets metadata on the thread being created to track ownership 2. Returns a filter that ensures only the creator can access it """ + # Example value: + # {'thread_id': UUID('99b045bc-b90b-41a8-b882-dabc541cf740'), 'metadata': {}, 'if_exists': 'raise'} + # Add owner metadata to the thread being created # This metadata is stored with the thread and persists metadata = value.setdefault("metadata", {}) metadata["owner"] = ctx.user.identity + # Return filter to restrict access to just the creator return {"owner": ctx.user.identity} @@ -210,6 +263,14 @@ async def on_assistants( ): # For illustration purposes, we will deny all requests # that touch the assistants resource + # Example value: + # { + # 'assistant_id': UUID('63ba56c3-b074-4212-96e2-cc333bbc4eb4'), + # 'graph_id': 'agent', + # 'config': {}, + # 'metadata': {}, + # 'name': 'Untitled' + # } raise Auth.exceptions.HTTPException( status_code=403, detail="User lacks the required permissions.",