diff --git a/docs/docs/cloud/reference/sdk/python_sdk_ref.md b/docs/docs/cloud/reference/sdk/python_sdk_ref.md index 17a539eae..1a0d0d4ef 100644 --- a/docs/docs/cloud/reference/sdk/python_sdk_ref.md +++ b/docs/docs/cloud/reference/sdk/python_sdk_ref.md @@ -9,4 +9,10 @@ ::: langgraph_sdk.auth + handler: python + +::: langgraph_sdk.auth.types + handler: python + +::: langgraph_sdk.auth.exceptions handler: python \ No newline at end of file diff --git a/libs/sdk-py/langgraph_sdk/auth/__init__.py b/libs/sdk-py/langgraph_sdk/auth/__init__.py index c72ba3c8c..5b9c8c985 100644 --- a/libs/sdk-py/langgraph_sdk/auth/__init__.py +++ b/libs/sdk-py/langgraph_sdk/auth/__init__.py @@ -11,20 +11,41 @@ AH = typing.TypeVar("AH", bound=types.Authenticator) class Auth: - """Authentication and authorization management for LangGraph. + """Add custom authentication and authorization management to your LangGraph application. The Auth class provides a unified system for handling authentication and - authorization in LangGraph applications. It supports: + authorization in LangGraph applications. It supports custom user authentication + protocols and fine-grained authorization rules for different resources and + actions. + + To use, create a separate python file and add the path to the file to your + LangGraph API configuration file (`langgraph.json`). Within that file, create + an instance of the Auth class and register authentication and authorization + handlers as needed. + + Example `langgraph.json` file: + + ```json + { + "dependencies": ["."], + "graphs": { + "agent": "./my_agent/agent.py:graph" + }, + "env": ".env", + "auth": { + "path": "./auth.py:my_auth" + } + ``` + + Then the LangGraph server will load your auth file and run it server-side whenever a request comes in. - 1. Authentication via a decorator-based handler system - 2. Fine-grained authorization rules for different resources and actions - 3. Global and resource-specific authorization handlers ???+ example "Basic Usage" ```python + # auth.py from langgraph_sdk import Auth - auth = Auth() + my_auth = Auth() async def verify_token(token: str) -> str: # Verify token and return user_id @@ -34,7 +55,12 @@ class Auth: @auth.authenticate async def authenticate(authorization: str) -> str: # Verify token and return user_id - return await verify_token(authorization) + result = await verify_token(authorization) + if result != "user_id": + raise Auth.exceptions.HTTPException( + status_code=401, detail="Unauthorized" + ) + return result # Global fallback handler @auth.on @@ -45,11 +71,11 @@ class Auth: async def authorize_thread_create(params: Auth.on.threads.create.value): # Allow the allowed user to create a thread assert params.get("metadata", {}).get("owner") == "allowed_user" - ``` ???+ note "Request Processing Flow" - 1. Authentication is performed first on every request + 1. Authentication (your `@auth.authenticate` handler) is performed first on **every request** 2. For authorization, the most specific matching handler is called: + - If a handler exists for the exact resource and action, it is used - Otherwise, if a handler exists for the resource with any action, it is used - Finally, if no specific handlers match, the global handler is used (if any) @@ -75,10 +101,64 @@ class Auth: """Reference to auth exception definitions. Provides access to all exception definitions used in the auth system, - like HTTPException, etc.""" + like HTTPException, etc. + """ def __init__(self) -> None: self.on = _On(self) + """Entry point for authorization handlers that control access to specific resources. + + The on class provides a flexible way to define authorization rules for different + resources and actions in your application. It supports three main usage patterns: + + 1. Global handlers that run for all resources and actions + 2. Resource-specific handlers that run for all actions on a resource + 3. Resource and action specific handlers for fine-grained control + + Each handler must be an async function that accepts two parameters: + - ctx (AuthContext): Contains request context and authenticated user info + - value: The data being authorized (type varies by endpoint) + + The handler should return one of: + + - None or True: Accept the request + - False: Reject with 403 error + - FilterType: Apply filtering rules to the response + + ???+ example "Examples" + Global handler for all requests: + ```python + @auth.on + async def reject_unhandled_requests(ctx: AuthContext, value: Any) -> None: + print(f"Request to {ctx.path} by {ctx.user.identity}") + return False + ``` + + Resource-specific handler. This would take precedence over the global handler + for all actions on the `threads` resource: + ```python + @auth.on.threads + async def check_thread_access(ctx: AuthContext, value: Any) -> bool: + # Allow access only to threads created by the user + return value.get("created_by") == ctx.user.identity + ``` + + Resource and action specific handler: + ```python + @auth.on.threads.delete + async def prevent_thread_deletion(ctx: AuthContext, value: Any) -> bool: + # Only admins can delete threads + return "admin" in ctx.user.permissions + ``` + + Multiple resources or actions: + ```python + @auth.on(resources=["threads", "runs"], actions=["create", "update"]) + async def rate_limit_writes(ctx: AuthContext, value: Any) -> bool: + # Implement rate limiting for write operations + return await check_rate_limit(ctx.user.identity) + ``` + """ # These are accessed by the API. Changes to their names or types is # will be considered a breaking change. self._handlers: dict[tuple[str, str], list[types.Handler]] = {} @@ -92,6 +172,7 @@ class Auth: The authentication handler is responsible for verifying credentials and returning user scopes. It can accept any of the following parameters by name: + - request (Request): The raw ASGI request object - body (dict): The parsed request body - path (str): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream" @@ -151,6 +232,7 @@ class Auth: "permissions": permissions, "display_name": user["name"], } + ``` """ if self._authenticate_handler is not None: raise ValueError( @@ -398,9 +480,9 @@ class _On: - value: The data being authorized (type varies by endpoint) The handler should return one of: - - None or True: Accept the request - - False: Reject with 403 error - - FilterType: Apply filtering rules to the response + - None or True: Accept the request + - False: Reject with 403 error + - FilterType: Apply filtering rules to the response ???+ example "Examples" diff --git a/libs/sdk-py/langgraph_sdk/auth/types.py b/libs/sdk-py/langgraph_sdk/auth/types.py index 4903b0c71..9a22ed9e1 100644 --- a/libs/sdk-py/langgraph_sdk/auth/types.py +++ b/libs/sdk-py/langgraph_sdk/auth/types.py @@ -701,16 +701,18 @@ class on: ```python from langgraph_sdk import Auth - @Auth.on + auth = Auth() + + @auth.on def handle_all(params: Auth.on.value): raise Exception("Not authorized") - @Auth.on.threads.create + @auth.on.threads.create def handle_thread_create(params: Auth.on.threads.create.value): # Handle thread creation pass - @Auth.on.assistants.search + @auth.on.assistants.search def handle_assistant_search(params: Auth.on.assistants.search.value): # Handle assistant search pass