Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/calling-apis/chatbot/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ OPENAI_API_KEY=xx-xxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx

# LANGGRAPH
LANGGRAPH_API_URL=http://localhost:54367
# Auth0 Resource Server Client Configuration (for token exchange with Token Vault)
# Auth0 Custom API Client Configuration (for token exchange with Token Vault)
# These credentials belong to a special "resource_server" client that can perform token exchanges
# on behalf of the user within your Langgraph API
RESOURCE_SERVER_CLIENT_ID="<your-resource-server-client-id>"
RESOURCE_SERVER_CLIENT_SECRET="<your-resource-server-client-secret>"
# on behalf of the user within your Langgraph API. Only needed for Langgraph example.
AUTH0_CUSTOM_API_CLIENT_ID="<your-custom-api-client-id>"
AUTH0_CUSTOM_API_CLIENT_SECRET="<your-custom-api-client-secret>"

#remove node.js deprecations warnings
NODE_OPTIONS="--no-deprecation"
97 changes: 72 additions & 25 deletions examples/calling-apis/chatbot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,74 @@ This is a [Next.js](https://nextjs.org) application that implements [Auth0 AI](h
- In your Auth0 Dashboard, go to Applications > APIs
- Create a new API with an identifier (audience).
- Ensure that "Allow Offline Access" is enabled for your API if you are using a flow which still makes use of refresh tokens.
- A Resource Server Client for performing token exchanges with Token Vault on behalf of a user. This will be used by the Langgraph API server (@langchain/langgraph-cli or Langgraph Platform) when executing tools that require third-party access.
- Create the Resource Server Client using the Management API:
```
curl -L 'https://{tenant}.auth0.com/api/v2/clients' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer {MANAGEMENT_API_TOKEN}' \
-d '{
"name": "Calendar API Resource Server Client",
"app_type": "resource_server",
"grant_types": ["urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token"],
"resource_server_identifier": "YOUR_API_IDENTIFIER"
}'
```
- Your `MANAGEMENT_API_TOKEN` above must have the `create:clients` scope in order to create a new client. To create a new Management API token with the right access permissions:
- Navigate to Applications > APIs > Auth0 Management API > API Explorer tab in your tenant.
- Click the Create & Authorize Test Application button.
- Copy the JWT access token shown and provide it as the `MANAGEMENT_API_TOKEN`.
- Use the audience that you provided when creating the API for the Langgraph API Server as the `resource_server_identifier`.
- Note down the `client_id` and `client_secret` returned from the cURL response for your environment variables after running cURL successfully.
- Either **Google**, **Slack** or **Github** social connections enabled for the application.
- A Custom API Client for performing token exchanges with Token Vault on behalf of a user. This will be used by the Langgraph API server (@langchain/langgraph-cli or Langgraph Platform) when executing tools that require third-party access.
- On the settings page for the previously created API, click the "Add Application" button in the header and create the Custom API Client.
- Ensure that the `Token Vault` grant type is enabled under the Advanced Settings.
- Note down the "Client ID" and "Client Secret" of this newly created Custom API Client.
- Either **Google**, **Slack** or **GitHub** social connections enabled for the application.
- For Google, make sure to enable `Offline Access` from the Connection Permissions settings.

### Pre-requisite: Grant access to My Account API from your web application

When a call to Token Vault fails due to the user not having a connected account (or lacking some permissions), this demo triggers a Connect Account flow for this user. This flow leverages Auth0's [My Account API](https://auth0.com/docs/manage-users/my-account-api), and as such, your application will need to have access to it in order to enable this flow.

n order to grant access, use the [Application Access to APIs](https://auth0.com/docs/get-started/applications/application-access-to-apis-client-grants) feature, by creating a client grant for user flows.

- In your Auth0 Dashboard, go to APIs, and open the Settings for "Auth0 My Account API".
- On the Settings tab, make sure to enable the "Allow Skipping User Consent" toggle.
- On the Applications tab, authorize your web application, ensuring that the `create:me:connected_accounts` permission at least is selected.

### Pre-requisite: Define a Multi-Resource Refresh Token policy for your web application

After your web application has been granted access to the My Account API, you will also need to leverage the [Multi-Resource Refresh Token](https://auth0.com/docs/secure/tokens/refresh-tokens/multi-resource-refresh-token) feature, where the refresh token delivered to your application will also allow it to obtain an access token to call My Account API.

This will require defining a new [refresh token policy](https://auth0.com/docs/secure/tokens/refresh-tokens/multi-resource-refresh-token/configure-and-implement-multi-resource-refresh-token) for your web application where the `audience` is `https://<your auth0 domain>/me/` and the `scope` should include at least the `"create:me:connected_accounts"` scope.

The documentation page explains how to achieve this using various tools, but here is an example showing how to do it with `curl`:

```shell
curl --request PATCH \
--url 'https://{yourDomain}/api/v2/clients/{yourClientId}' \
--header 'authorization: Bearer {yourMgmtApiAccessToken}' \
--header 'content-type: application/json' \
--data '{
"refresh_token": {
"expiration_type": "expiring",
"rotation_type": "rotating",
"token_lifetime": 31557600,
"idle_token_lifetime": 2592000,
"leeway": 0,
"infinite_token_lifetime": false,
"infinite_idle_token_lifetime": false,
"policies": [
{
"audience": "https://{yourDomain}/me/",
"scope": [
"create:me:connected_accounts"
]
}
]
}
}'
```
Where:
- `{yourDomain}` is your Auth0 domain (e.g., `dev-abc123.us.auth0.com`).
- `{yourClientId}` is the Client ID of your web application.
- `{yourMgmtApiAccessToken}` is a Management API access token with the `update:clients` scope.

<details>

<summary>How to get a Management API Token from the Dashboard</summary>

To create a token exchange profile, you need a Management API access token with the appropriate scopes.

The quickest way to get a token for testing is from the Auth0 Dashboard:
* Navigate to Applications > APIs in your Auth0 Dashboard
* Select Auth0 Management API
* Click on the API Explorer tab
* Copy the displayed token

</details>

### Setup the workspace `.env` file

Expand All @@ -54,7 +101,7 @@ AUTH0_SECRET="<use [openssl rand -hex 32] to generate a 32 bytes value>"
APP_BASE_URL=http://localhost:3000
# the offline_access scope is needed if your flow is using a refresh token
AUTH0_SCOPE='openid profile email offline_access'
# Langgraph API audience. Only needed for Langgraph example.
# Langgraph API audience (only needed for Langgraph example)
AUTH0_AUDIENCE="<auth0-audience>"
NEXT_PUBLIC_URL="http://localhost:3000"

Expand All @@ -63,11 +110,11 @@ OPENAI_API_KEY=xx-xxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx

# LANGGRAPH
LANGGRAPH_API_URL=http://localhost:54367
# Auth0 Resource Server Client Configuration (for token exchange with Token Vault)
# Auth0 Custom API Client Configuration (for token exchange with Token Vault)
# These credentials belong to a special "resource_server" client that can perform token exchanges
# on behalf of the user within your Langgraph API. Only needed for Langgraph example.
RESOURCE_SERVER_CLIENT_ID="<your-resource-server-client-id>"
RESOURCE_SERVER_CLIENT_SECRET="<your-resource-server-client-secret>"
AUTH0_CUSTOM_API_CLIENT_ID="<your-custom-api-client-id>"
AUTH0_CUSTOM_API_CLIENT_SECRET="<your-custom-api-client-secret>"
```

> [!NOTE]
Expand Down
4 changes: 2 additions & 2 deletions examples/calling-apis/chatbot/app/(ai-sdk)/lib/auth0-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const withGoogleCalendar = auth0AI.withTokenVault({
return refreshToken;
},
connection: "google-oauth2",
scopes: ["https://www.googleapis.com/auth/calendar.freebusy"],
scopes: ["openid", "https://www.googleapis.com/auth/calendar.freebusy"],
});

export const withSlack = auth0AI.withTokenVault({
Expand Down Expand Up @@ -40,5 +40,5 @@ export const withGoogleDriveTools = auth0AI.withTokenVault({
return refreshToken;
},
connection: "google-oauth2",
scopes: ["https://www.googleapis.com/auth/drive"],
scopes: ["openid", "https://www.googleapis.com/auth/drive"],
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { tool } from "ai";
import { addHours, formatISO } from "date-fns";
import { addDays, formatISO } from "date-fns";
import { GaxiosError } from "gaxios";
import { google } from "googleapis";
import { z } from "zod/v3";
Expand Down Expand Up @@ -32,7 +32,7 @@ export const checkUsersCalendar = withGoogleCalendar(
auth,
requestBody: {
timeMin: formatISO(date),
timeMax: addHours(date, 1).toISOString(),
timeMax: addDays(date, 1).toISOString(),
timeZone: "UTC",
items: [{ id: "primary" }],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { TokenVaultError } from "@auth0/ai/interrupts";

export const listRepositories = withGitHub(
tool({
description: "List respositories for the current user on GitHub",
description: "List repositories for the current user on GitHub",
inputSchema: z.object({}),
execute: async () => {
// Get the access token from Auth0 AI
Expand Down
2 changes: 1 addition & 1 deletion examples/calling-apis/chatbot/app/(genkit)/lib/auth0-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const withTokenForGoogleConnection = auth0AI.withTokenVault({
return refreshToken;
},
connection: "google-oauth2",
scopes: ["https://www.googleapis.com/auth/calendar.freebusy"],
scopes: ["openid", "https://www.googleapis.com/auth/calendar.freebusy"],
});

export const withSlack = auth0AI.withTokenVault({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addHours, formatISO } from "date-fns";
import { addDays, formatISO } from "date-fns";
import { GaxiosError } from "gaxios";
import { google } from "googleapis";
import { z } from "zod/v3";
Expand Down Expand Up @@ -40,7 +40,7 @@ export const checkUsersCalendar = ai.defineTool(
auth,
requestBody: {
timeMin: formatISO(date),
timeMax: addHours(date, 1).toISOString(),
timeMax: addDays(date, 1).toISOString(),
timeZone: "UTC",
items: [{ id: "primary" }],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { TokenVaultError } from "@auth0/ai/interrupts";
export const listRepositories = ai.defineTool(
...withGitHub(
{
description: "List respositories for the current user on GitHub",
description: "List repositories for the current user on GitHub",
inputSchema: z.object({}),
name: "listRepositories",
},
Expand Down
4 changes: 2 additions & 2 deletions examples/calling-apis/chatbot/app/(langgraph)/lib/auth0-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Auth0AI } from "@auth0/ai-langchain";
const auth0AI = new Auth0AI({
auth0: {
domain: process.env.AUTH0_DOMAIN!,
clientId: process.env.RESOURCE_SERVER_CLIENT_ID!, // Resource server client ID for token exchange
clientSecret: process.env.RESOURCE_SERVER_CLIENT_SECRET!, // Resource server client secret
clientId: process.env.AUTH0_CUSTOM_API_CLIENT_ID!, // Custom API Client ID for token exchange
clientSecret: process.env.AUTH0_CUSTOM_API_CLIENT_SECRET!, // Custom API Client secret
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addHours, formatISO } from "date-fns";
import { addDays, formatISO } from "date-fns";
import { GaxiosError } from "gaxios";
import { google } from "googleapis";
import { z } from "zod/v3";
Expand Down Expand Up @@ -27,7 +27,7 @@ export const checkUsersCalendar = withGoogleCalendar(
auth,
requestBody: {
timeMin: formatISO(date),
timeMax: addHours(date, 1).toISOString(),
timeMax: addDays(date, 1).toISOString(),
timeZone: "UTC",
items: [{ id: "primary" }],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const listRepositories = withGitHub(
},
{
name: "list_github_repositories",
description: "List respositories for the current user on GitHub",
description: "List repositories for the current user on GitHub",
schema: z.object({}),
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const withGoogleCalendar = auth0AI.withTokenVault({
return refreshToken;
},
connection: "google-oauth2",
scopes: ["https://www.googleapis.com/auth/calendar.freebusy"],
scopes: ["openid", "https://www.googleapis.com/auth/calendar.freebusy"],
});

export const withSlack = auth0AI.withTokenVault({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addHours, formatISO } from "date-fns";
import { addDays, formatISO } from "date-fns";
import { GaxiosError } from "gaxios";
import { google } from "googleapis";
import { tool } from "llamaindex";
Expand Down Expand Up @@ -28,7 +28,7 @@ export const checkUsersCalendar = () =>
auth,
requestBody: {
timeMin: formatISO(date),
timeMax: addHours(date, 1).toISOString(),
timeMax: addDays(date, 1).toISOString(),
timeZone: "UTC",
items: [{ id: "primary" }],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const listRepositories = () =>
},
{
name: "listRepositories",
description: "List respositories for the current user on GitHub",
description: "List repositories for the current user on GitHub",
parameters: z.object({}),
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";
import { ReactNode } from "react";
import type { ReactNode } from "react";

/**
* Defines the mode the TokenVaultConsent component will use to prompt the user to authorize the API access.
* Defines the mode the TokenVaultConsent component will use to prompt the user to connect a third-party account and
* authorize the API access.
* - `redirect` will redirect the user to the provider's authorization page.
* - `popup` will open a popup window to prompt the user to authorize the API access.
* - `auto` will automatically choose the best mode based on the user's device and browser.
Expand All @@ -13,10 +14,11 @@ export type TokenVaultAuthProps = {
interrupt: {
connection: string;
requiredScopes: string[];
authorizationParams?: Record<string, string>;
resume?: () => void;
};
auth?: {
authorizePath?: string;
connectPath?: string;
returnTo?: string;
};
onFinish?: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BrowserView, MobileView } from "react-device-detect";

import { TokenVaultConsentPopup } from "./popup";
import { TokenVaultConsentRedirect } from "./redirect";
import { TokenVaultAuthProps } from "./TokenVaultAuthProps";
import type { TokenVaultAuthProps } from "./TokenVaultAuthProps";

export function TokenVaultConsent(props: TokenVaultAuthProps) {
const { mode } = props;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { useCallback, useEffect, useState } from "react";

import { WaitingMessage } from "../util/loader";
import { PromptUserContainer } from "../util/prompt-user-container";
import { TokenVaultAuthProps } from "./TokenVaultAuthProps";

import type { TokenVaultAuthProps } from "./TokenVaultAuthProps";

export function TokenVaultConsentPopup({
interrupt: { connection, requiredScopes, resume },
interrupt: { connection, requiredScopes, authorizationParams, resume },
connectWidget: { icon, title, description, action, containerClassName },
auth: { authorizePath = "/auth/login", returnTo = "/close" } = {},
auth: { connectPath = "/auth/connect", returnTo = "/close" } = {},
onFinish,
}: TokenVaultAuthProps) {
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -43,14 +44,17 @@ export function TokenVaultConsentPopup({
//Open the login popup
const startLoginPopup = useCallback(async () => {
const search = new URLSearchParams({
returnTo,
connection,
access_type: "offline",
prompt: "consent",
connection_scope: requiredScopes.join(),
returnTo,
// Add all extra authorization parameters to the search params, they will be collected and submitted via the
// authorization_params parameter of the connect account flow.
...authorizationParams,
});
for (const requiredScope of requiredScopes) {
search.append("scopes", requiredScope);
}

const url = new URL(authorizePath, window.location.origin);
const url = new URL(connectPath, window.location.origin);
url.search = search.toString();

const windowFeatures =
Expand All @@ -63,7 +67,7 @@ export function TokenVaultConsentPopup({
setLoginPopup(popup);
setIsLoading(true);
}
}, [connection, requiredScopes]);
}, [connection, requiredScopes, returnTo, authorizationParams, connectPath]);

if (isLoading) {
return <WaitingMessage />;
Expand Down
Loading