- Spec files
pypi-langgraph-sdk
Describes: pkg:pypi/langgraph-sdk@0.2.x
- Description
- Python SDK for interacting with the LangGraph Platform REST API to build and manage AI assistants and conversational workflows
- Author
- tessl
- Last updated
authentication.md docs/
1# Authentication & Authorization23Comprehensive authentication and authorization system supporting custom authentication handlers, fine-grained authorization rules, and flexible security policies for all resources and actions.45## Capabilities67### Authentication System89Custom authentication handlers for verifying user credentials and extracting user information from requests.1011```python { .api }12from typing import Callable, TypeVar13from collections.abc import Sequence14from langgraph_sdk.auth import types, exceptions1516TH = TypeVar("TH", bound=types.Handler)17AH = TypeVar("AH", bound=types.Authenticator)1819class Auth:20"""Add custom authentication and authorization management to your LangGraph application.2122The Auth class provides a unified system for handling authentication and23authorization in LangGraph applications. It supports custom user authentication24protocols and fine-grained authorization rules for different resources and25actions.26"""2728types = types29"""Reference to auth type definitions.3031Provides access to all type definitions used in the auth system,32like ThreadsCreate, AssistantsRead, etc."""3334exceptions = exceptions35"""Reference to auth exception definitions.3637Provides access to all exception definitions used in the auth system,38like HTTPException, etc.39"""4041def __init__(self) -> None:42self.on: _On = ... # Authorization handlers4344def authenticate(self, fn: AH) -> AH:45"""Register an authentication handler function.4647The authentication handler is responsible for verifying credentials48and returning user scopes. It can accept any of the following parameters49by name:5051- request (Request): The raw ASGI request object52- body (dict): The parsed request body53- path (str): The request path54- method (str): The HTTP method55- path_params (dict[str, str]): URL path parameters56- query_params (dict[str, str]): URL query parameters57- headers (dict[bytes, bytes]): Request headers58- authorization (str | None): The Authorization header value5960Args:61fn: The authentication handler function to register.62Must return a representation of the user. This could be a:63- string (the user id)64- dict containing {"identity": str, "permissions": list[str]}65- or an object with identity and permissions properties6667Returns:68The registered handler function.69"""70```7172### Authorization Handlers7374Fine-grained authorization control with resource-specific and action-specific handlers.7576```python { .api }77class _On:78"""Authorization handler registration system."""7980assistants: _AssistantsOn81threads: _ThreadsOn82crons: _CronsOn83store: _StoreOn8485def __call__(86self,87fn: Callable = None,88*,89resources: str | list[str] = None,90actions: str | list[str] = None91) -> Callable:92"""93Register global or filtered authorization handler.9495Parameters:96- fn: Handler function (for direct decoration)97- resources: Resource names to handle98- actions: Action names to handle99100Returns:101Handler function or decorator102"""103104# Resource-specific authorization handlers105class _AssistantsOn:106"""Authorization handlers for assistant operations."""107108create: Callable # @auth.on.assistants.create109read: Callable # @auth.on.assistants.read110update: Callable # @auth.on.assistants.update111delete: Callable # @auth.on.assistants.delete112search: Callable # @auth.on.assistants.search113114def __call__(self, fn: Callable) -> Callable:115"""Handle all assistant operations: @auth.on.assistants"""116117class _ThreadsOn:118"""Authorization handlers for thread operations."""119120create: Callable # @auth.on.threads.create121read: Callable # @auth.on.threads.read122update: Callable # @auth.on.threads.update123delete: Callable # @auth.on.threads.delete124search: Callable # @auth.on.threads.search125create_run: Callable # @auth.on.threads.create_run126127def __call__(self, fn: Callable) -> Callable:128"""Handle all thread operations: @auth.on.threads"""129130class _CronsOn:131"""Authorization handlers for cron operations."""132133create: Callable # @auth.on.crons.create134read: Callable # @auth.on.crons.read135update: Callable # @auth.on.crons.update136delete: Callable # @auth.on.crons.delete137search: Callable # @auth.on.crons.search138139def __call__(self, fn: Callable) -> Callable:140"""Handle all cron operations: @auth.on.crons"""141142class _StoreOn:143"""Authorization handlers for store operations."""144145def __call__(146self,147fn: Callable = None,148*,149actions: str | list[str] = None150) -> Callable:151"""152Handle store operations.153154Parameters:155- fn: Handler function156- actions: Specific store actions ("put", "get", "search", "list_namespaces", "delete")157"""158```159160### Authentication Types161162Type definitions for user representation and authentication context.163164```python { .api }165from typing import Protocol, TypedDict, Callable, Union, Literal, Any166from collections.abc import Sequence, Awaitable, Mapping167import typing_extensions168169class MinimalUser(Protocol):170"""User objects must at least expose the identity property."""171172@property173def identity(self) -> str:174"""The unique identifier for the user."""175...176177class BaseUser(Protocol):178"""The base ASGI user protocol."""179180@property181def is_authenticated(self) -> bool:182"""Whether the user is authenticated."""183...184185@property186def display_name(self) -> str:187"""The display name of the user."""188...189190@property191def identity(self) -> str:192"""The unique identifier for the user."""193...194195@property196def permissions(self) -> Sequence[str]:197"""The permissions associated with the user."""198...199200class StudioUser:201"""A user object that's populated from authenticated requests from the LangGraph studio."""202identity: str203display_name: str204is_authenticated: bool = True205kind: Literal["StudioUser"] = "StudioUser"206207class BaseAuthContext:208"""Base class for authentication context."""209permissions: Sequence[str]210user: BaseUser211212class AuthContext(BaseAuthContext):213"""Complete authentication context with resource and action information."""214resource: Literal["runs", "threads", "crons", "assistants", "store"]215action: Literal["create", "read", "update", "delete", "search", "create_run", "put", "get", "list_namespaces"]216217class MinimalUserDict(TypedDict, total=False):218"""The dictionary representation of a user."""219identity: typing_extensions.Required[str]220display_name: str221is_authenticated: bool222permissions: Sequence[str]223224# Filter types for authorization responses225FilterType = Union[226dict[str, Union[str, dict[Literal["$eq", "$contains"], str]]],227dict[str, str],228]229230HandlerResult = Union[None, bool, FilterType]231"""The result of a handler can be:232* None | True: accept the request.233* False: reject the request with a 403 error234* FilterType: filter to apply235"""236237Handler = Callable[..., Awaitable[HandlerResult]]238239Authenticator = Callable[240...,241Awaitable[242Union[MinimalUser, str, BaseUser, MinimalUserDict, Mapping[str, Any]],243],244]245"""Type for authentication functions."""246```247248### Authorization Data Types249250Type definitions for operation-specific authorization data.251252```python { .api }253# Thread operation types254class ThreadsCreate(TypedDict):255metadata: dict256thread_id: str257thread_ttl: int258259class ThreadsRead(TypedDict):260thread_id: str261262class ThreadsUpdate(TypedDict):263thread_id: str264metadata: dict265266class ThreadsDelete(TypedDict):267thread_id: str268269class ThreadsSearch(TypedDict):270metadata: dict271values: dict272status: str273274# Assistant operation types275class AssistantsCreate(TypedDict):276graph_id: str277config: dict278metadata: dict279280class AssistantsRead(TypedDict):281assistant_id: str282283class AssistantsUpdate(TypedDict):284assistant_id: str285config: dict286metadata: dict287288class AssistantsDelete(TypedDict):289assistant_id: str290291class AssistantsSearch(TypedDict):292metadata: dict293graph_id: str294295# Run operation types296class RunsCreate(TypedDict):297thread_id: str298assistant_id: str299input: dict300config: dict301302# Cron operation types303class CronsCreate(TypedDict):304assistant_id: str305schedule: str306thread_id: str307config: dict308309class CronsRead(TypedDict):310cron_id: str311312class CronsDelete(TypedDict):313cron_id: str314315class CronsSearch(TypedDict):316assistant_id: str317thread_id: str318319# Store operation types320class StoreGet(TypedDict):321namespace: list[str]322key: str323324class StorePut(TypedDict):325namespace: list[str]326key: str327value: dict328329class StoreDelete(TypedDict):330namespace: list[str]331key: str332333class StoreSearch(TypedDict):334namespace_prefix: list[str]335query: str336filter: dict337338class StoreListNamespaces(TypedDict):339prefix: list[str]340suffix: list[str]341```342343### Exception Types344345```python { .api }346class HTTPException(Exception):347"""HTTP exception for authentication/authorization errors."""348349def __init__(self, status_code: int, detail: str):350self.status_code = status_code351self.detail = detail352super().__init__(detail)353```354355## Usage Examples356357### Basic Authentication Setup358359```python360from langgraph_sdk import Auth361362# Create auth instance363auth = Auth()364365@auth.authenticate366async def authenticate(authorization: str) -> str:367"""368Simple token-based authentication.369370Returns user ID if token is valid.371"""372if not authorization or not authorization.startswith("Bearer "):373raise Auth.exceptions.HTTPException(374status_code=401,375detail="Missing or invalid authorization header"376)377378token = authorization[7:] # Remove "Bearer "379user_id = await verify_token(token) # Your token verification logic380381if not user_id:382raise Auth.exceptions.HTTPException(383status_code=401,384detail="Invalid token"385)386387return user_id388389async def verify_token(token: str) -> str:390"""Your token verification implementation."""391# Call your auth service, check database, etc.392if token == "valid-token-123":393return "user-123"394return None395```396397### Advanced Authentication with Permissions398399```python400@auth.authenticate401async def authenticate(402authorization: str,403path: str,404method: str405) -> Auth.types.MinimalUserDict:406"""407Authentication with user permissions.408"""409if not authorization:410raise Auth.exceptions.HTTPException(401, "Authorization required")411412# Verify token and get user info413user_data = await verify_jwt_token(authorization)414415return {416"identity": user_data["user_id"],417"permissions": user_data.get("permissions", []),418"display_name": user_data.get("name", "Unknown User")419}420421async def verify_jwt_token(authorization: str) -> dict:422"""Verify JWT and return user data."""423# JWT verification logic424import jwt425426try:427token = authorization.replace("Bearer ", "")428payload = jwt.decode(token, "secret", algorithms=["HS256"])429return payload430except jwt.InvalidTokenError:431raise Auth.exceptions.HTTPException(401, "Invalid token")432```433434### Authorization Handlers435436```python437# Global authorization handler438@auth.on439async def global_auth(ctx: Auth.types.AuthContext, value: dict) -> bool:440"""441Global handler for all requests.442Applied when no specific handler matches.443"""444# Log all requests445print(f"Request: {ctx.method} {ctx.path} by {ctx.user.identity}")446447# Allow all requests (specific handlers can override)448return True449450# Resource-specific handlers451@auth.on.threads452async def thread_auth(ctx: Auth.types.AuthContext, value: dict) -> bool:453"""454Handle all thread operations.455More specific than global handler.456"""457# Users can only access their own threads458thread_metadata = value.get("metadata", {})459owner = thread_metadata.get("owner")460461return owner == ctx.user.identity462463# Action-specific handlers464@auth.on.threads.create465async def thread_create_auth(466ctx: Auth.types.AuthContext,467value: Auth.types.ThreadsCreate468) -> bool:469"""470Handle thread creation specifically.471Most specific handler.472"""473# Check user has permission to create threads474return "threads:create" in getattr(ctx.user, "permissions", [])475476@auth.on.threads.delete477async def thread_delete_auth(478ctx: Auth.types.AuthContext,479value: Auth.types.ThreadsDelete480) -> bool:481"""482Restrict thread deletion to admins.483"""484return "admin" in getattr(ctx.user, "permissions", [])485```486487### Store Authorization488489```python490@auth.on.store491async def store_auth(ctx: Auth.types.AuthContext, value: dict) -> bool:492"""493Authorize store operations.494Enforce user isolation in namespaces.495"""496namespace = value.get("namespace", [])497498# Ensure user can only access their own namespace499if len(namespace) >= 2 and namespace[0] == "users":500return namespace[1] == ctx.user.identity501502# Allow access to shared namespaces for certain users503if namespace[0] in ["shared", "public"]:504return True505506# Admins can access everything507return "admin" in getattr(ctx.user, "permissions", [])508509# Action-specific store handlers510@auth.on.store(actions=["put", "delete"])511async def store_write_auth(ctx: Auth.types.AuthContext, value: dict) -> bool:512"""513Restrict write operations.514"""515# Only users with write permission can modify data516return "store:write" in getattr(ctx.user, "permissions", [])517518@auth.on.store(actions=["search", "list_namespaces"])519async def store_search_auth(ctx: Auth.types.AuthContext, value: dict) -> bool:520"""521Allow search operations for authenticated users.522"""523return not getattr(ctx.user, "is_anonymous", False)524```525526### Advanced Authorization Patterns527528```python529# Multi-resource authorization530@auth.on(resources=["threads", "runs"], actions=["create", "update"])531async def rate_limit_writes(ctx: Auth.types.AuthContext, value: dict) -> bool:532"""533Rate limit write operations across resources.534"""535user_id = ctx.user.identity536537# Check rate limit538current_minute = datetime.now().strftime("%Y-%m-%d %H:%M")539rate_key = f"rate_limit:{user_id}:{current_minute}"540541current_count = await redis_client.get(rate_key) or 0542if int(current_count) >= 100: # 100 writes per minute543raise Auth.exceptions.HTTPException(544status_code=429,545detail="Rate limit exceeded"546)547548await redis_client.incr(rate_key)549await redis_client.expire(rate_key, 60)550551return True552553# Conditional authorization554@auth.on.assistants.update555async def assistant_update_auth(556ctx: Auth.types.AuthContext,557value: Auth.types.AssistantsUpdate558) -> bool:559"""560Allow assistant updates based on ownership or admin role.561"""562assistant_id = value["assistant_id"]563564# Get assistant metadata565assistant = await get_assistant_metadata(assistant_id)566567# Owner can always update568if assistant.get("owner") == ctx.user.identity:569return True570571# Admins can update any assistant572if "admin" in getattr(ctx.user, "permissions", []):573return True574575# Collaborators can update if they have permission576collaborators = assistant.get("collaborators", [])577return ctx.user.identity in collaborators578579# Data filtering authorization580@auth.on.threads.search581async def thread_search_filter(582ctx: Auth.types.AuthContext,583value: Auth.types.ThreadsSearch584) -> dict:585"""586Filter search results based on user permissions.587Returns filter dict instead of boolean.588"""589# Regular users can only see their own threads590if "admin" not in getattr(ctx.user, "permissions", []):591return {592"metadata.owner": ctx.user.identity593}594595# Admins see everything596return {}597```598599### Configuration Integration600601```python602# Example langgraph.json configuration603"""604{605"dependencies": ["."],606"graphs": {607"my_assistant": "./assistant.py:graph"608},609"env": ".env",610"auth": {611"path": "./auth.py:auth"612}613}614"""615616# auth.py file617auth = Auth()618619@auth.authenticate620async def authenticate(authorization: str) -> Auth.types.MinimalUserDict:621# Your authentication logic622return await verify_user(authorization)623624@auth.on625async def default_auth(ctx: Auth.types.AuthContext, value: dict) -> bool:626# Default authorization logic627return True628629# Export the auth instance630__all__ = ["auth"]631```