Skip to content

Commit

Permalink
Feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
hinthornw committed Dec 18, 2024
1 parent 1e07a9a commit 7f0bfdc
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 210 deletions.
3 changes: 2 additions & 1 deletion docs/docs/cloud/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,5 @@ RUN set -ex && \

RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*

ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
```
14 changes: 1 addition & 13 deletions docs/docs/cloud/reference/sdk/python_sdk_ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,7 @@
::: langgraph_sdk.auth
handler: python

::: langgraph_sdk.auth.types.Authenticator
handler: python

::: langgraph_sdk.auth.types.Handler
handler: python

::: langgraph_sdk.auth.types.HandlerResult
handler: python

::: langgraph_sdk.auth.types.FilterType
handler: python

::: langgraph_sdk.auth.types.AuthContext
::: langgraph_sdk.auth.types
handler: python

::: langgraph_sdk.auth.exceptions
Expand Down
214 changes: 168 additions & 46 deletions docs/docs/concepts/auth.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/docs/how-tos/auth/custom_auth.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# How to add custom authentication

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.

!!! tip "Prerequisites"

This guide assumes familiarity with the following concepts:
Expand All @@ -11,10 +9,12 @@ This guide shows how to add custom authentication to your LangGraph Platform app

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

!!! note "Python only"
???+ 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.

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.

## 1. Implement authentication

Create `auth.py` file, with a basic JWT authentication handler:
Expand Down
167 changes: 64 additions & 103 deletions docs/docs/tutorials/auth/add_auth_server.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
# Connecting an Authentication Provider
# Connecting an Authentication Provider (Part 3/3)

In the previous tutorial, we added [resource authorization](../../concepts/auth.md#resource-authorization) to give users private conversations. However, we were still using hard-coded tokens for authentication, which is not secure. Now we'll replace those tokens with real user accounts using [OAuth2](../../concepts/auth.md#oauth2-authentication).
!!! note "This is part 3 of our authentication series:"

1. [Basic Authentication](getting_started.md) - Control who can access your bot
2. [Resource Authorization](resource_auth.md) - Let users have private conversations
3. Production Auth (you are here) - Add real user accounts and validate using OAuth2

In the [Making Conversations Private](resource_auth.md) tutorial, we added [resource authorization](../../concepts/auth.md#resource-authorization) to give users private conversations. However, we were still using hard-coded tokens for authentication, which is not secure. Now we'll replace those tokens with real user accounts using [OAuth2](../../concepts/auth.md#oauth2-authentication).

We'll keep the same [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object and [resource-level access control](../../concepts/auth.md#resource-level-access-control), but upgrade our authentication to use Supabase as our identity provider. While we use Supabase in this tutorial, the concepts apply to any OAuth2 provider. You'll learn how to:

1. Replace test tokens with real [JWT tokens](../../concepts/auth.md#jwt-tokens)
2. Integrate with OAuth2 providers for secure user authentication
3. Handle user sessions and metadata while maintaining our existing authorization logic

!!! note "This is part 3 of our authentication series:"

1. [Basic Authentication](getting_started.md) - Control who can access your bot
2. [Resource Authorization](resource_auth.md) - Let users have private conversations
3. Production Auth (you are here) - Add real user accounts and validate using OAuth2

!!! warning "Prerequisites"
## Requirements

- [Create a Supabase project](https://supabase.com/dashboard)
- Have your project URL and service role key ready
You will need to set up a Supabase project to use its authentication server for this tutorial. You can do so [here](https://supabase.com/dashboard).

## Background

Expand Down Expand Up @@ -49,7 +48,7 @@ sequenceDiagram
In the following example, we'll use Supabase as our auth server. The LangGraph application will provide the backend for your app, and we will write test code for the client app.
Let's get started!

## Setting Up Authentication Provider
## Setting Up Authentication Provider {#setup-auth-provider}

First, let's install the required dependencies. Start in your `custom-auth` directory and ensure you have the `langgraph-cli` installed:

Expand Down Expand Up @@ -153,30 +152,29 @@ Let's test this with a real user account!

## Testing Authentication Flow

Create a new file `create_users.py`. This will stand-in for a frontend that lets users sign up and log in.
Let's test out our new authentication flow. You can run the following code in a file or notebook.

```python
import argparse
import asyncio
import os

import dotenv
import httpx
from getpass import getpass
from langgraph_sdk import get_client

dotenv.load_dotenv()

# Get email from command line
parser = argparse.ArgumentParser()
parser.add_argument("email", help="Your email address for testing")
args = parser.parse_args()

base_email = args.email.split("@")
email = getpass("Enter your email: ")
base_email = email.split("@")
password = "secure-password" # CHANGEME
email1 = f"{base_email[0]}+1@{base_email[1]}"
email2 = f"{base_email[0]}+2@{base_email[1]}"

SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_SERVICE_KEY = os.environ["SUPABASE_SERVICE_KEY"]
SUPABASE_URL = os.environ.get("SUPABASE_URL")
if not SUPABASE_URL:
SUPABASE_URL = getpass("Enter your Supabase project URL: ")

SUPABASE_SERVICE_KEY = os.environ.get("SUPABASE_SERVICE_KEY")
if not SUPABASE_SERVICE_KEY:
SUPABASE_SERVICE_KEY = getpass("Enter your Supabase service role key: ")


async def sign_up(email: str, password: str):
Expand All @@ -190,55 +188,30 @@ async def sign_up(email: str, password: str):
assert response.status_code == 200
return response.json()

async def main():
# Create two test users
password = "secure-password" # CHANGEME
print(f"Creating test users: {email1} and {email2}")
await sign_up(email1, password)
await sign_up(email2, password)

if __name__ == "__main__":
asyncio.run(main())
# Create two test users
print(f"Creating test users: {email1} and {email2}")
await sign_up(email1, password)
await sign_up(email2, password)
```

Then run the setup script:

```shell
python create_users.py [email protected]
```
Then run the code.

!!! tip "About test emails"
We'll create two test accounts by adding "+1" and "+2" to your email. For example, if you use "[email protected]", we'll create "[email protected]" and "[email protected]". All emails will be delivered to your original address.

⚠️ Before continuing: Check your email and click both confirmation links. This would normally be handled by your frontend.
⚠️ Before continuing: Check your email and click both confirmation links.

Now let's test that users can only see their own data. Create a new file `test_oauth.py`. This will stand-in for your application's frontend.
Now let's test that users can only see their own data. Make sure the server is running (run `langgraph dev`) before proceeding. The following snippet requires the "anon public" key that you copied from the Supabase dashboard while [setting up the auth provider](#setup-auth-provider) previously.

```python
import argparse
import asyncio
import os

import dotenv
import httpx
from langgraph_sdk import get_client

dotenv.load_dotenv()

# Get email from command line
parser = argparse.ArgumentParser()
parser.add_argument("email", help="Your email address for testing")
args = parser.parse_args()

# Create two test emails from the base email
base_email = args.email.split("@")
email1 = f"{base_email[0]}+1@{base_email[1]}"
email2 = f"{base_email[0]}+2@{base_email[1]}"

# Initialize auth provider settings
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_ANON_KEY = os.environ["SUPABASE_ANON_KEY"]
from langgraph_sdk import get_client

SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY")
if not SUPABASE_ANON_KEY:
SUPABASE_ANON_KEY = getpass("Enter your Supabase anon key: ")

async def login(email: str, password: str):
"""Get an access token for an existing user."""
Expand All @@ -260,49 +233,37 @@ async def login(email: str, password: str):
raise ValueError(f"Login failed: {response.status_code} - {response.text}")


async def main():
password = "secure-password"

# Log in as user 1
user1_token = await login(email1, password)
user1_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user1_token}"}
)

# Create a thread as user 1
thread = await user1_client.threads.create()
print(f"✅ User 1 created thread: {thread['thread_id']}")

# Try to access without a token
unauthenticated_client = get_client(url="http://localhost:2024")
try:
await unauthenticated_client.threads.create()
print("❌ Unauthenticated access should fail!")
except Exception as e:
print("✅ Unauthenticated access blocked:", e)

# Try to access user 1's thread as user 2
user2_token = await login(email2, password)
user2_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user2_token}"}
)

try:
await user2_client.threads.get(thread["thread_id"])
print("❌ User 2 shouldn't see User 1's thread!")
except Exception as e:
print("✅ User 2 blocked from User 1's thread:", e)


if __name__ == "__main__":
asyncio.run(main())
```

Fetch the SUPABASE_ANON_KEY that you copied from the Supabase dashboard in step (1), then run the test. Make sure the server is running (if you have run `langgraph dev`):

```bash
python test_oauth.py [email protected]
# Log in as user 1
user1_token = await login(email1, password)
user1_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user1_token}"}
)

# Create a thread as user 1
thread = await user1_client.threads.create()
print(f"✅ User 1 created thread: {thread['thread_id']}")

# Try to access without a token
unauthenticated_client = get_client(url="http://localhost:2024")
try:
await unauthenticated_client.threads.create()
print("❌ Unauthenticated access should fail!")
except Exception as e:
print("✅ Unauthenticated access blocked:", e)

# Try to access user 1's thread as user 2
user2_token = await login(email2, password)
user2_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user2_token}"}
)

try:
await user2_client.threads.get(thread["thread_id"])
print("❌ User 2 shouldn't see User 1's thread!")
except Exception as e:
print("✅ User 2 blocked from User 1's thread:", e)
```
The output should look like this:

> ➜ custom-auth SUPABASE_ANON_KEY=eyJh... python test_oauth.py [email protected]
> ✅ User 1 created thread: d6af3754-95df-4176-aa10-dbd8dca40f1a
Expand Down
24 changes: 12 additions & 12 deletions docs/docs/tutorials/auth/getting_started.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Setting up Custom Authentication

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.
# Setting up Custom Authentication (Part 1/3)

!!! note "This is part 1 of our authentication series:"

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

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.

## Setting up our project

First, let's create a new chatbot using the LangGraph starter template:
Expand All @@ -23,22 +23,22 @@ The template gives us a placeholder LangGraph app. Let's try it out by installin
pip install -e .
langgraph dev
```
If everything works, the server should start and open the studio in your browser.

> - 🚀 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.
If everything works, the server should start and open the studio in your browser.

Now that we've seen the base LangGraph app, let's add authentication to it!
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.

## Adding Authentication

The [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.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.

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:
Create a new file `src/security/auth.py`. This is where our code will live to check if users are allowed to access our bot:

```python
from langgraph_sdk import Auth
Expand Down Expand Up @@ -72,7 +72,7 @@ async def get_current_user(authorization: str | None) -> Auth.types.MinimalUserD
Notice that our authentication handler does two important things:

1. Checks if a valid token is provided
2. Returns the user's
2. Returns the user's identity

Now tell LangGraph to use our authentication by adding the following to the `langgraph.json` configuration:

Expand All @@ -84,7 +84,7 @@ Now tell LangGraph to use our authentication by adding the following to the `lan
}
```

## Testing Our Secure Bot
## Testing Our "Secure" Bot

Let's start the server again to test everything out!

Expand All @@ -99,8 +99,8 @@ langgraph dev --no-browser
```json
{
"auth": {
"path": "src/security/auth.py:auth",
"disable_studio_auth": "true"
"path": "src/security/auth.py:auth",
"disable_studio_auth": "true"
}
}
```
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/tutorials/auth/resource_auth.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Making Conversations Private

In this tutorial, we will extend our chatbot to give each user their own private conversations. We'll add [resource-level access control](../../concepts/auth.md#resource-level-access-control) so users can only see their own threads.
# Making Conversations Private (Part 2/3)

!!! note "This is part 2 of our authentication series:"

1. [Basic Authentication](getting_started.md) - Control who can access your bot
2. Resource Authorization (you are here) - Let users have private conversations
3. [Production Auth](add_auth_server.md) - Add real user accounts and validate using OAuth2

In this tutorial, we will extend our chatbot to give each user their own private conversations. We'll add [resource-level access control](../../concepts/auth.md#resource-level-access-control) so users can only see their own threads.

## 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).
Expand Down Expand Up @@ -267,4 +267,4 @@ Now that you can control access to resources, you might want to:

1. Move on to [Production Auth](add_auth_server.md) to add real user accounts
2. Read more about [authorization patterns](../../concepts/auth.md#authorization)
3. Try adding shared resources between users
3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) for details about the interfaces and methods used in this tutorial
Loading

0 comments on commit 7f0bfdc

Please sign in to comment.