Skip to content

Commit

Permalink
Fix cross linking
Browse files Browse the repository at this point in the history
  • Loading branch information
hinthornw committed Dec 18, 2024
1 parent 97b3d1b commit a7dbefa
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 110 deletions.
103 changes: 57 additions & 46 deletions docs/docs/how-tos/auth/custom_auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@

This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Cloud, BYOC, and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.

<!-- TODO: Add prerequisites/cross-links -->
!!! tip "Prerequisites"

This guide assumes familiarity with the following concepts:

* [**Authentication & Access Control**](../../concepts/auth.md)
* [**LangGraph Platform**](../../concepts/index.md#langgraph-platform)

For a more guided walkthrough, see [**setting up custom authentication**](../../tutorials/auth/getting_started.md) tutorial.

!!! 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.

## 1. Implement authentication

Expand Down Expand Up @@ -63,60 +74,60 @@ Assuming you are using JWT token authentication, you could access your deploymen

=== "Python Client"

```python
from langgraph_sdk import get_client
# generate token with your auth provider
my_token = "your-token"
client = get_client(
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await client.threads.list()
```
```python
from langgraph_sdk import get_client

my_token = "your-token" # In practice, you would generate a signed token with your auth provider
client = get_client(
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await client.threads.list()
```

=== "Python RemoteGraph"

```python
from langgraph.pregel.remote import RemoteGraph
# generate token with your auth provider
my_token = "your-token"
remote_graph = RemoteGraph(
"agent",
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await remote_graph.threads.list()
```
```python
from langgraph.pregel.remote import RemoteGraph
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
remote_graph = RemoteGraph(
"agent",
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await remote_graph.threads.list()
```

=== "JavaScript Client"

```javascript
import { Client } from "@langchain/langgraph-sdk";
// generate token with your auth provider
const my_token = "your-token";
const client = new Client({
apiUrl: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await client.threads.list();
```
```javascript
import { Client } from "@langchain/langgraph-sdk";

const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const client = new Client({
apiUrl: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await client.threads.list();
```

=== "JavaScript RemoteGraph"

```javascript
import { RemoteGraph } from "@langchain/langgraph/remote";
// generate token with your auth provider
const my_token = "your-token";
const remoteGraph = new RemoteGraph({
graphId: "agent",
url: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await remoteGraph.threads.list();
```
```javascript
import { RemoteGraph } from "@langchain/langgraph/remote";

const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const remoteGraph = new RemoteGraph({
graphId: "agent",
url: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await remoteGraph.threads.list();
```

=== "CURL"

```bash
curl -H "Authorization: Bearer your-token" http://localhost:2024/threads
```
```bash
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
```
136 changes: 72 additions & 64 deletions docs/docs/tutorials/auth/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@

Let's add custom 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.

??? note "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.
???+ tip "Prerequisites"
This guide assumes familiarity with the following concepts:

* The [LangGraph Platform](../../concepts/index.md#langgraph-platform)
* [Authentication & Access Control](../../concepts/auth.md) in the LangGraph Platform

!!! note "Prerequisites"

Before you begin, ensure you have the following:
- [GitHub account](https://github.com/)
- [LangSmith account](https://smith.langchain.com/)
- [Supabase account](https://supabase.com/)
- [Anthropic API key](https://console.anthropic.com/)

* [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.

## Understanding authentication flow

Expand All @@ -38,34 +48,53 @@ langgraph new --template=new-langgraph-project-python custom-auth
cd custom-auth
```

### 2. Create the auth handler
### 2. Set up environment variables

Next, let's create our authentication handler. Create a new file at `src/security/auth.py`:
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
```

To get your Supabase credentials:

1. Create a project at [supabase.com](https://supabase.com)
2. Go to Project Settings > API
3. Add these credentials to your `.env` file:

Also note down your project's "anon public" key. We'll use this for client authentication below.

### 3. Create the auth handler

Next, let's create our authentication handler. This registers functions two functions: one to authenticate a user to the service and one to authorize that user to access a resource (e.g., a thread). We will use the [Auth](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object from the langgraph_sdk to do this.

Create a new file at `src/security/auth.py`:

```python
import os
import httpx
from langgraph_sdk import Auth

# Load from your .env file
# 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"]

# Create the auth object we'll use to protect our endpoints
# The auth handler registers functions that the LangGraph backend will call
auth = Auth()

@auth.authenticate
async def get_current_user(
authorization: str | None, # "Bearer <token>"
) -> tuple[list[str], Auth.types.MinimalUserDict]:
"""Verify the JWT token and return user info."""
if not authorization:
raise Auth.exceptions.HTTPException(
status_code=401,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)

try:
# Fetch the user info from Supabase
async with httpx.AsyncClient() as client:
Expand All @@ -76,12 +105,7 @@ async def get_current_user(
"apiKey": SUPABASE_SERVICE_KEY,
},
)
if response.status_code != 200:
raise Auth.exceptions.HTTPException(
status_code=401,
detail="Invalid token"
)

assert response.status_code == 200
user_data = response.json()
return {
"identity": user_data["id"],
Expand All @@ -95,7 +119,7 @@ async def get_current_user(
)
```

This handler ensures only users with valid tokens can access our server. However, all users can still see each other's threads. Let's fix that by adding an authorization filter to the bottom of `auth.py`:
This handler ensures only users with valid tokens can access our server. If we only provide `authentication`, users can still see all threads and other resources once they've authenticated. Let's add some metadata filters to limit access further. We will do this using an authorization function that adds the user ID to the metadata and then returning that metadata as a filter for all resources. We will register this function to the LangGraph platform using the `@auth.on` decorator. Check out the [reference documentation](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) for more details.

```python
@auth.on
Expand All @@ -112,9 +136,9 @@ async def add_owner(

Now when users create threads, their ID is automatically added as the owner, and they can only see threads they own.

### 2. Configure LangGraph
### 3. Configure `langgraph.json`

Next, tell LangGraph about our auth handler. Open `langgraph.json` and add:
Next, we need to tell LangGraph that we've created an auth handler. Open `langgraph.json` and add:

```json
{
Expand All @@ -126,40 +150,23 @@ Next, tell LangGraph about our auth handler. Open `langgraph.json` and add:

This points LangGraph to our `auth` object in the `auth.py` file.

### 3. Set up environment variables

Copy the example env file and add your Supabase credentials. To get your Supabase credentials:

1. Create a new project at [supabase.com](https://supabase.com)
2. Go to Project Settings > API to find your project's credentials
3. Add these credentials to your `.env` file:

```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
```

Also note down your project's "anon public" key - we'll use this for client authentication below.

### 4. Start the server

Install dependencies and start LangGraph:

```bash
```shell
pip install -e .
langgraph dev --no-browser
```

## Interacting with the server

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. You can use a single email with "+" to create multiple users, e.g. "[email protected]" and "[email protected]".
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.

!!! tip "Multiple example emails"
You can create multiple users with a shared email bya dding a "+" to the email address. For example, "[email protected]" can be used to create "[email protected]" and "[email protected]".

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.

```python
import os
Expand All @@ -168,9 +175,7 @@ import dotenv

from langgraph_sdk import get_client

dotenv.load_dotenv()

supabase_url: str = os.environ.get("SUPABASE_URL")
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
Expand Down Expand Up @@ -256,7 +261,9 @@ thread = await client.threads.get(thread["thread_id"])
print(f"\nThread:\n{thread}")
```

Now let's see what happens when we try to access without authentication:
We were able to create a thread and have a conversation with the bot. Great!

Now let's see what happens when we try to access the server without authentication:

```python
# Try to access without a token
Expand All @@ -267,7 +274,9 @@ except Exception as e:
print(f"Failed without token: {e}") # Will show 403 Forbidden
```

Finally, let's try accessing user 1's thread as user 2:
Without an authentication token, we couldn't create a new thread!

If we try to access a thread owned by another user, we'll get an error:

```python
# Log in as user 2
Expand All @@ -280,17 +289,20 @@ user_2_client = get_client(
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
```

This demonstrates that:
Notice that:

1. With a valid token, we can create and interact with threads
2. Without a token, we get a 401 Unauthorized or 403 Forbidden error
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
Expand All @@ -302,12 +314,8 @@ Now that you've set everything up, you can deploy your LangGraph application to
3. Connect to your GitHub repository and copy the contents of your `.env` file as environment variables.
4. Click "Submit".

Once deployed, you should be able to run the code above, replacing the `http://localhost:2024` with the URL of your deployment.
Once deployed, you should be able to run the client code above again, replacing the `http://localhost:2024` with the URL of your deployment.

## Next steps

Now that you understand token-based authentication:

1. Add password hashing and secure user management
2. Add user-specific resource ownership (see [resource access control](./resource_access.md))
3. Implement more advanced auth patterns
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.

0 comments on commit a7dbefa

Please sign in to comment.