From b85c9961d7280062714d124ec649b168c42bcbc2 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:50:38 -0800 Subject: [PATCH] Split into 3 --- .../cloud/reference/sdk/python_sdk_ref.md | 1 - docs/docs/tutorials/auth/getting_started.md | 367 +++++------------- docs/docs/tutorials/index.md | 6 +- docs/mkdocs.yml | 2 + 4 files changed, 103 insertions(+), 273 deletions(-) diff --git a/docs/docs/cloud/reference/sdk/python_sdk_ref.md b/docs/docs/cloud/reference/sdk/python_sdk_ref.md index 4126ca306..3fde9e9cb 100644 --- a/docs/docs/cloud/reference/sdk/python_sdk_ref.md +++ b/docs/docs/cloud/reference/sdk/python_sdk_ref.md @@ -7,7 +7,6 @@ ::: langgraph_sdk.schema handler: python - ::: langgraph_sdk.auth handler: python diff --git a/docs/docs/tutorials/auth/getting_started.md b/docs/docs/tutorials/auth/getting_started.md index 5b5edac2d..7517b3058 100644 --- a/docs/docs/tutorials/auth/getting_started.md +++ b/docs/docs/tutorials/auth/getting_started.md @@ -1,153 +1,80 @@ -# Setting up custom authentication +# Setting up Custom Authentication -Let's add OAuth2 token authentication to a LangGraph template. This lets users interact with our bot making their conversations accessible to other users. This tutorial covers the core concepts of token-based authentication and show how to integrate with an authentication server. +In this tutorial, we will build a chatbot that only lets specific users access it. We'll start with the LangGraph template and add token-based security step by step. By the end, you'll have a working chatbot that checks for valid tokens before allowing access. -???+ tip "Prerequisites" -This guide assumes familiarity with the following concepts: +!!! note "This is part 1 of our authentication series:" - * The [LangGraph Platform](../../concepts/index.md#langgraph-platform) - * [Authentication & Access Control](../../concepts/auth.md) in the LangGraph Platform + 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](supabase_auth.md) - Add real user accounts and validate using OAuth2 +## Setting up our project - Before you begin, ensure you have the following: +First, let's create a new chatbot using the LangGraph starter template: - * [GitHub account](https://github.com/) - * [LangSmith account](https://smith.langchain.com/) - * [Supabase account](https://supabase.com/) - * [Anthropic API key](https://console.anthropic.com/) - -??? note "Python only" - - We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.11`. Support for LangGraph.JS will be added soon. - -??? tip "Default authentication" -When deploying to LangGraph Cloud, requests are authenticated using LangSmith API keys by default. This gates access to the server but doesn't provide fine-grained access control over threads. Self-hosted LangGraph platform has no default authentication. This guide shows how to add custom authentication handlers that work in both cases, to provide fine-grained access control over threads, runs, and other resources. - -## Overview - -The key components in a token-based authentication system are: - -1. **Auth server**: manages users and generates signed tokens (could be Supabase, Auth0, or your own server) -2. **Client**: gets tokens from auth server and includes them in requests. This is typically the user's browser or mobile app. -3. **LangGraph backend**: validates tokens and enforces access control to control access to your agents and data. - -For a typical interaction: - -1. User authenticates with the auth server (username/password, OAuth, "Sign in with Google", etc.) -2. Auth server returns a signed JWT token attesting "I am user X with claims/roles Y" -3. User includes this token in request headers to LangGraph -4. LangGraph validates token signature and checks claims against the auth server. If valid, it allows the request, using custom filters to restrict access only to the user's resources. - - -## 1. Clone the template - -Clone the [LangGraph template](https://github.com/langchain-ai/new-langgraph-project) to get started. - -```shell +```bash pip install -U "langgraph-cli[inmem]" langgraph new --template=new-langgraph-project-python custom-auth cd custom-auth ``` -### 2. Set up environment variables - -Copy the example `.env` file and add your Supabase credentials. - -```bash -cp .env.example .env -``` - -Add to your `.env`: - -```bash -SUPABASE_URL=https://your-project.supabase.co -SUPABASE_SERVICE_KEY=your-service-key # aka the service_role secret -ANTHROPIC_API_KEY=your-anthropic-key # For the LLM in our chatbot +The template gives us a placeholder LangGraph app. Let's try it out by installing the local dependencies and running the development server. +```shell +pip install -e . +langgraph dev ``` +> - 🚀 API: http://127.0.0.1:2024 +> - 🎨 Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024 +> - 📚 API Docs: http://127.0.0.1:2024/docs +> +> This in-memory server is designed for development and testing. +> For production use, please use LangGraph Cloud. -To get your Supabase credentials: +If everything works, the server should start and open the studio in your browser. -1. Create a project at [supabase.com](https://supabase.com) -2. Go to Project Settings > API -3. Add these credentials to your `.env` file: +Now that we've seen the base LangGraph app, let's add authentication to it! -Also note down your project's "anon public" key. We'll use this for client authentication below. +## Adding Authentication -### 3. Create the auth handler +The `Auth` object lets you register an authentication function that the LangGraph platform will run on every request. This function receives each request and decides whether to accept or reject. -Now we'll create an authentication handler that does two things: -1. Authenticates users by validating their tokens (`@auth.authenticate`) -2. Controls what resources those users can access (`@auth.on`) - -We'll use the `Auth` class from `langgraph_sdk` to register these handler functions. The LangGraph backend will automatically call these functions that you've registered whenever a user makes a request. - -Create a new file at `src/security/auth.py`: +Create a new file `src/security/auth.py`. This is where we'll our code will live to check if users are allowed to access our bot: ```python -import os -import httpx from langgraph_sdk import Auth -# These will be loaded from your .env file in the next step -SUPABASE_URL = os.environ["SUPABASE_URL"] -SUPABASE_SERVICE_KEY = os.environ["SUPABASE_SERVICE_KEY"] +# This is our toy user database +VALID_TOKENS = { + "user1-token": {"id": "user1", "name": "Alice"}, + "user2-token": {"id": "user2", "name": "Bob"}, +} -# The auth handler registers functions that the LangGraph backend will call auth = Auth() -@auth.authenticate -async def get_current_user( - authorization: str | None, # "Bearer " -) -> tuple[list[str], Auth.types.MinimalUserDict]: - """Verify the JWT token and return user info.""" - try: - # Fetch the user info from Supabase - async with httpx.AsyncClient() as client: - response = await client.get( - f"{SUPABASE_URL}/auth/v1/user", - headers={ - "Authorization": authorization, - "apiKey": SUPABASE_SERVICE_KEY, - }, - ) - assert response.status_code == 200 - user_data = response.json() - return { - "identity": user_data["id"], - "display_name": user_data.get("name"), - "is_authenticated": True, - } - except Exception as e: - raise Auth.exceptions.HTTPException( - status_code=401, - detail="Invalid token" - ) -``` -This handler validates the user's information, but by itself doesn't restrict what authenticated users can access. Let's add an authorization handler to limit access to resources. We'll do this by: -1. Adding the user's ID to resource metadata when they create something -2. Using that metadata to filter what resources they can see - -Register this authorization handler with the `@auth.on` decorator. This function will run on all calls that make it past the authentication stage. - -```python -@auth.on -async def add_owner( - ctx: Auth.types.AuthContext, - value: dict, -): - """Add owner to resource metadata and filter by owner.""" - filters = {"owner": ctx.user.identity} - metadata = value.setdefault("metadata", {}) - metadata.update(filters) - return filters +@auth.authenticate +async def get_current_user(authorization: str | None) -> Auth.types.MinimalUserDict: + """Check if the user's token is valid.""" + assert authorization + scheme, token = authorization.split() + assert scheme.lower() == "bearer" + # Check if token is valid + if token not in VALID_TOKENS: + raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid token") + + # Return user info if valid + user_data = VALID_TOKENS[token] + return { + "identity": user_data["id"], + } ``` -Now when users create threads, assistants, runs, or other resources, their ID is automatically added as the owner in its metadata, and they can only see the threads they own. +Notice that our authentication handler does two important things: -### 3. Configure `langgraph.json` +1. Checks if a valid token is provided +2. Returns the user's -Next, we need to tell LangGraph that we've created an auth handler. Open `langgraph.json` and add: +Now tell LangGraph to use our authentication by adding the following to the `langgraph.json` configuration: ```json { @@ -157,174 +84,74 @@ Next, we need to tell LangGraph that we've created an auth handler. Open `langgr } ``` -This points LangGraph to our `auth` object in the `auth.py` file. +## Testing Our Secure Bot -### 4. Start the server +Let's start the server again to test everything out! -Install dependencies and start LangGraph: - -```shell -pip install -e . +```bash langgraph dev --no-browser ``` -## Interacting with the server +??? note "Custom auth in the studio" -First, let's set up our environment and helper functions. Fill in the values for your Supabase anon key, and provide a working email address for our test users. + If you didn't add the `--no-browser`, the studio UI will open in the browser. You may wonder, how is the studio able to still connect to our server? By default, we also permit access from the LangGraph studio, even when using custom auth. This makes it easier to develop and test your bot in the studio. You can remove this alternative authentication option by + setting `disable_studio_auth: "true"` in your auth configuration: + ```json + { + "auth": { + "path": "src/security/auth.py:auth", + "disable_studio_auth": "true" + } + } + ``` -!!! tip "Multiple example emails" -You can create multiple users with a shared email bya dding a "+" to the email address. For example, "myemail@gmail.com" can be used to create "myemail+1@gmail.com" and "myemail+2@gmail.com". - -Copy the code below. Make sure to fill out the Supabase URL & anon key, as well as the email addresses for your test users. Then run the code. +Now let's try to chat with our bot. Create a new file `test_auth.py`: ```python -import os -import httpx -import dotenv - +import asyncio from langgraph_sdk import get_client -supabase_url: str = "CHANGEME" -supabase_anon_key: str = "CHANGEME" # Your project's anon/public key -user_1_email = "CHANGEME" # Your test email -user_2_email = "CHANGEME" # A second test email -password = "password" # Very secure! :) - -# Helper functions for authentication -async def sign_up(email, password): - async with httpx.AsyncClient() as client: - response = await client.post( - f"{supabase_url}/auth/v1/signup", - headers={ - "apikey": supabase_anon_key, - "Content-Type": "application/json", - }, - json={ - "email": email, - "password": password - } - ) - if response.status_code == 200: - return response.json() - else: - raise ValueError("Sign up failed:", response.status_code, response.text) - -async def login(email, password): - async with httpx.AsyncClient() as client: - response = await client.post( - f"{supabase_url}/auth/v1/token?grant_type=password", - headers={ - "apikey": supabase_anon_key, - "Content-Type": "application/json", - }, - json={ - "email": email, - "password": password - } - ) - if response.status_code == 200: - return response.json() - else: - raise ValueError("Login failed:", response.status_code, response.text) -``` - -Now let's create two test users: - -```python -# Create our test users -await sign_up(user_1_email, password) -await sign_up(user_2_email, password) -``` -⚠️ Before continuing: Check your email for both addresses and click the confirmation links. Don't worry about any error pages you might see from the confirmation redirect - those would normally be handled by your frontend. - -Now let's log in as our first user and create a thread: - -```python -# Log in as user 1 -user_1_login_data = await login(user_1_email, password) -user_1_token = user_1_login_data["access_token"] - -# Create an authenticated client -client = get_client( - url="http://localhost:2024", - headers={"Authorization": f"Bearer {user_1_token}"} -) - -# Create a thread and chat with the bot -thread = await client.threads.create() -print(f'Created thread: {thread["thread_id"]}') - -# Have a conversation -async for event, (chunk, metadata) in client.runs.stream( - thread_id=thread["thread_id"], - assistant_id="agent", - input={"messages": [{"role": "user", "content": "Tell me a short joke"}]}, - stream_mode="messages-tuple", -): - if event == "messages" and metadata["langgraph_node"] == "chatbot": - print(chunk['content'], end="", flush=True) - -# View the thread history -thread = await client.threads.get(thread["thread_id"]) -print(f"\nThread:\n{thread}") -``` - -We were able to create a thread and have a conversation with the bot. Great! +async def test_auth(): + # Try without a token (should fail) + client = get_client(url="http://localhost:2024") + try: + thread = await client.threads.create() + print("❌ Should have failed without token!") + except Exception as e: + print("✅ Correctly blocked access:", e) -Now let's see what happens when we try to access the server without authentication: + # Try with a valid token + client = get_client( + url="http://localhost:2024", headers={"Authorization": "Bearer user1-token"} + ) -```python -# Try to access without a token -unauthenticated_client = get_client(url="http://localhost:2024") -try: - await unauthenticated_client.threads.create() -except Exception as e: - print(f"Failed without token: {e}") # Will show 403 Forbidden -``` + # Create a thread and chat + thread = await client.threads.create() + print(f"✅ Created thread as Alice: {thread['thread_id']}") -Without an authentication token, we couldn't create a new thread! + response = await client.runs.create( + thread_id=thread["thread_id"], + assistant_id="agent", + input={"messages": [{"role": "user", "content": "Hello!"}]}, + ) + print("✅ Bot responded:") + print(response) -If we try to access a thread owned by another user, we'll get an error: -```python -# Log in as user 2 -user_2_login_data = await login(user_2_email, password) -user_2_token = user_2_login_data["access_token"] - -# Create client for user 2 -user_2_client = get_client( - url="http://localhost:2024", - headers={"Authorization": f"Bearer {user_2_token}"} -) - -# This passes -thread2 = await unauthenticated_client.threads.create() - -# Try to access user 1's thread -try: - await user_2_client.threads.get(thread["thread_id"]) -except Exception as e: - print(f"Failed to access other user's thread: {e}") # Will show 404 Not Found +if __name__ == "__main__": + asyncio.run(test_auth()) ``` -Notice that: - -1. With a valid token, we can create and interact with threads -2. Without a token, we get an authentication error saying we are forbidden -3. Even with a valid token, users can only access their own threads - -## Deploying to LangGraph Cloud - -Now that you've set everything up, you can deploy your LangGraph application to LangGraph Cloud! Simply: - -1. Push your code to a new github repository. -2. Navigate to the LangGraph Platform page and click "+New Deployment". -3. Connect to your GitHub repository and copy the contents of your `.env` file as environment variables. -4. Click "Submit". +Run the test code and you should see that: +1. Without a valid token, we can't access the bot +2. With a valid token, we can create threads and chat -Once deployed, you should be able to run the client code above again, replacing the `http://localhost:2024` with the URL of your deployment. +Congratulations! You've built a chatbot that only lets "authorized" users access it. While this system doesn't (yet) implement a production-ready security scheme, we've learned the basic mechanics of how to control access to our bot. In the next tutorial, we'll learn how to give each user their own private conversations. -## Next steps +## What's Next? -Now that you understand token-based authentication, you can try integrating this in actual frontend code! You can see a longer example of this tutorial at the [custom auth template](https://github.com/langchain-ai/custom-auth). There, you can see a full end-to-end example of adding custom authentication to a LangGraph chatbot using a react web frontend. \ No newline at end of file +Now that you can control who accesses your bot, you might want to: +1. Move on to [Resource Authorization](resource_auth.md) to learn how to make conversations private +2. Read more about [authentication concepts](../../concepts/auth.md) +3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md) for more authentication options \ No newline at end of file diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index ffd11fd1c..31fcfddbb 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -77,6 +77,8 @@ Explore practical implementations tailored for specific scenarios: ### Authentication & Access Control -Add custom authentication and authorization to your LangGraph Platform deployment. +Add custom authentication and authorization to an existing LangGraph Platform deployment in the following three-part guide: -- [Setting Up Custom Authentication](./auth/getting_started.md): Implement OAuth2 authentication to authorize users on your deployment \ No newline at end of file +1. [Setting Up Custom Authentication](./auth/getting_started.md): Implement OAuth2 authentication to authorize users on your deployment +2. [Resource Authorization](./auth/resource_auth.md): Let users have private conversations +3. [Connecting an Authentication Provider](./auth/supabase_auth.md): Add real user accounts and validate using OAuth2 \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b661ebe14..09bb726fe 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -144,6 +144,8 @@ nav: - LangGraph Platform: - LangGraph Platform: concepts#langgraph-platform - docs/docs/tutorials/auth/getting_started.md + - docs/docs/tutorials/auth/resource_auth.md + - docs/docs/tutorials/auth/add_auth_server.md - How-to Guides: - how-tos/index.md