> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ユーザーに代わってサードパーティ API を呼び出す

> Token Vault を使用して、AI agent を外部のツール、関数、サービスに接続します。

Auth0 SDKを使用して、Auth0の[Token Vault](https://auth0.com/docs/ja-jp/secure/call-apis-on-users-behalf/token-vault.md)からソーシャルおよびエンタープライズのIDプロバイダー用のアクセストークンを取得します。このアクセストークンを使えば、アプリケーションに統合されたAI agentがサードパーティ APIを呼び出し、ユーザーに代わってタスクを実行できます。

アプリケーションで[Organizations](https://auth0.com/docs/ja-jp/manage-users/organizations.md)を使用している場合は、Connected Accounts フローを開始する前に、対象のOrganizationでユーザーを認証してください。Organizationのメンバーは、各自で自分の外部アカウントを連携して認可します。

このクイックスタートを完了すると、Auth0と統合され、次のことができるAIアプリケーションが完成します。

1. Connected Accounts フローを開始し、ユーザーが自分のGoogleアカウントを連携してAI agentにアクセスを許可できるようにする。
2. Googleのソーシャル接続用のアクセストークンを取得する。
3. AI agentと統合してGoogle APIを呼び出す。

## 技術スタックを選択する

> [tab] LangGraph.js + Next.js
>
> Prerequisites
>
> Before getting started, make sure you have completed the following steps:
>
> > [tab] Use sample app (recommended)
> >
> > ### Download sample app
> >
> > Start by downloading and extracting the sample app. Then open in your preferred IDE.
> >
> > Download call-others-apis-on-users-behalf langchain-next-js sample code
> >
> > ### Create your environment file
> >
> > In the root directory of your project, create a new `.env.local` file and add the following content:
> >
> > ```bash .env.local wrap lines theme={null}
> > # Auth0 configuration
> > APP_BASE_URL='http://localhost:3000'
> > AUTH0_SECRET='random 32 byte value'
> > AUTH0_DOMAIN='<your-auth0-domain>'
> > AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
> > AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'
> >
> > AUTH0_AUDIENCE="https://your.domain.us.langgraph.app"
> > AUTH0_SCOPE="openid profile email offline_access"
> > AUTH0_CUSTOM_API_CLIENT_ID="{yourCustomApiClientId}"
> > AUTH0_CUSTOM_API_CLIENT_SECRET="{yourCustomApiClientSecret}"
> >
> > # OpenAI API Key or any provider supported by the Vercel AI SDK
> > OPENAI_API_KEY="YOUR_API_KEY"
> >
> > # LANGGRAPH
> > LANGGRAPH_API_URL=http://localhost:54367
> > LANGCHAIN_CALLBACKS_BACKGROUND=false
> > ```
> >
> > To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your Regular Web application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > To get your API's `AUTH0_AUDIENCE`, navigate to **[Applications > APIs](https://manage.auth0.com/#/apis)** in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.
> >
> > To get your Custom API Client's `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:
> >
> > ```bash generate random 32 byte value theme={null}
> > openssl rand -hex 32
> > ```
> >
> > Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:
> >
> > ```bash .env.local wrap lines theme={null}
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > ### Install packages
> >
> > Ensure you have `npm` installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation.
> > In the root directory of your project, run the following command to install the required packages:
> >
> > ```bash wrap lines theme={null}
> > npm install
> > ```
> >
> > ### Test your application
> >
> > Start the application with `npm run all:dev`. Then, navigate to `http://localhost:3000`.
> >
> > > [!NOTE]
> > >
> > > This opens LangGraph Studio in a new tab. You can safely close it.
> >
> > If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent to fetch emails from your Gmail account!
> >
> > That's it! You successfully called a third-party API using Token Vault.
>
> > [tab] Integrate into your app
> >
> > ### Install packages
> >
> > In the root directory of your project, install the following packages:
> >
> > * `@auth0/ai-langchain`: [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-langchain) built for AI agents powered by LangChain.
> > * `@langchain/langgraph`: For building stateful, multi-actor applications with LLMs.
> > * `langchain`: The LangChain library.
> > * `@langchain/core`: LangChain core libraries.
> > * `@langchain/openai`: OpenAI provider for LangChain.
> > * `@langchain/community`: LangChain community integrations.
> > * `langgraph-nextjs-api-passthrough`: API passthrough for LangGraph.
> >
> > ```bash wrap lines theme={null}
> > npm install @auth0/ai-langchain@4 @langchain/community@0.3.53 @langchain/core@0.3.77 @langchain/langgraph@0.4.4 @langchain/openai@0.6.13 langchain@0.3.12 langgraph-nextjs-api-passthrough@0.1.4
> > ```
> >
> > ### Update your environment file
> >
> > Add to your environment file the following variables:
> >
> > ```bash .env.local wrap lines theme={null}
> > # Auth0 configuration
> > APP_BASE_URL='http://localhost:3000'
> > AUTH0_SECRET='random 32 byte value'
> > AUTH0_DOMAIN='<your-auth0-domain>'
> > AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
> > AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'
> >
> > AUTH0_AUDIENCE="https://your.domain.us.langgraph.app"
> > AUTH0_CUSTOM_API_CLIENT_ID="{yourCustomApiClientId}"
> > AUTH0_CUSTOM_API_CLIENT_SECRET="{yourCustomApiClientSecret}"
> >
> > # OpenAI API Key or any provider supported by the Vercel AI SDK
> > OPENAI_API_KEY="YOUR_API_KEY"
> >
> > # LANGGRAPH
> > LANGGRAPH_API_URL=http://localhost:54367
> > LANGCHAIN_CALLBACKS_BACKGROUND=false
> > ```
> >
> > To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your Regular Web application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > To get your API's `AUTH0_AUDIENCE`, navigate to **[Applications > APIs](https://manage.auth0.com/#/apis)** in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.
> >
> > To get your Custom API Client's `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:
> >
> > ```bash generate random 32 byte value theme={null}
> > openssl rand -hex 32
> > ```
> >
> > Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:
> >
> > ```bash .env.local wrap lines theme={null}
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > ### Set up Token Vault for Google social connection
> >
> > Use the [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-langchain) to get an access token for the [Google Social Connection](https://auth0.com/ai/docs/connections/google.md) using [Token Vault](https://auth0.com/docs/secure/call-apis-on-users-behalf/token-vault.md):
> >
> > * `connection`: pass in the name of the connection you want to access.
> > * `scopes`: pass in the scopes to be authorized for this connection.
> >
> > Create a file `src/lib/auth0-ai.ts` to instantiate the Auth0 AI SDK client:
> >
> > ```tsx src/lib/auth0-ai.ts wrap lines highlight={17} theme={null}
> > import { Auth0AI } from '@auth0/ai-langchain';
> > import { SUBJECT_TOKEN_TYPES } from "@auth0/ai";
> >
> > const auth0AI = new Auth0AI({
> >   auth0: {
> >     domain: process.env.AUTH0_DOMAIN!,
> >     clientId: process.env.AUTH0_CUSTOM_API_CLIENT_ID!,
> >     clientSecret: process.env.AUTH0_CUSTOM_API_CLIENT_SECRET!,
> >   },
> > });
> >
> > const withAccessTokenForConnection = (connection: string, scopes: string[]) =>
> >   auth0AI.withTokenVault({
> >     connection,
> >     scopes,
> >     accessToken: async (_, config) => {
> >       return config.configurable?.langgraph_auth_user?.getRawAccessToken();
> >     },
> >     subjectTokenType: SUBJECT_TOKEN_TYPES.SUBJECT_TYPE_ACCESS_TOKEN,
> >   });
> >
> > // Connection for Google services
> > export const withGmailSearch = withAccessTokenForConnection(
> >   'google-oauth2',
> >   ['openid', 'https://www.googleapis.com/auth/gmail.readonly'],
> > );
> > ```
> >
> > ### Pass credentials to the tools
> >
> > Create a file `/src/lib/auth0.ts` file with the following code:
> >
> > ```tsx src/lib/auth0.ts wrap lines theme={null}
> > import { Auth0Client } from '@auth0/nextjs-auth0/server';
> >
> > export const auth0 = new Auth0Client({
> >   authorizationParameters: {
> >     scope: process.env.AUTH0_SCOPE,
> >     audience: process.env.AUTH0_AUDIENCE,
> >   },
> >   enableConnectAccountEndpoint: true,
> > });
> >
> > export const getAccessToken = async () => {
> >   const tokenResult = await auth0.getAccessToken();
> >
> >   if(!tokenResult || !tokenResult.token) {
> >     throw new Error("No access token found in Auth0 session");
> >   }
> >
> >   return tokenResult.token;
> > };
> > ```
> >
> > Update your chat route, typically at `/src/app/api/chat/[..._path]/route.ts` to pass the `accessToken` to your LangGraph agent to use the Auth0 AI SDK to get the Google access token from the server.
> >
> > ```ts src/app/api/chat/[..._path]/route.ts wrap lines highlight={3,10} theme={null}
> > import { initApiPassthrough } from "langgraph-nextjs-api-passthrough";
> >
> > import { getAccessToken } from "@/lib/auth0";
> >
> > export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } =
> >   initApiPassthrough({
> >     apiUrl: process.env.LANGGRAPH_API_URL,
> >     baseRoute: "chat/",
> >     headers: async () => {
> >       const accessToken = await getAccessToken();
> >       return {
> >         Authorization: `Bearer ${accessToken}`,
> >       };
> >   });
> > ```
> >
> > ### Add Custom Authentication
> >
> > > [!NOTE]
> > >
> > > For more information on how to add custom authentication for your LangGraph Platform application, read the [Custom Auth](https://langchain-ai.github.io/langgraphjs/how-tos/auth/custom_auth/) guide.
> >
> > In your `langgraph.json`, add the path to your auth file:
> >
> > ```typescript langgraph.json wrap lines highlight={8} theme={null}
> > {
> >   "node_version": "20",
> >   "graphs": {
> >     "agent": "./src/lib/agent.ts:agent"
> >   },
> >   "env": ".env",
> >   "auth": {
> >     "path": "./src/lib/auth.ts:authHandler"
> >   }
> > }
> > ```
> >
> > Then, in your `auth.ts` file, add your auth logic:
> >
> > ```typescript src/lib/auth.ts wrap lines theme={null}
> > import { createRemoteJWKSet, jwtVerify } from "jose";
> >
> > const { Auth, HTTPException } = require("@langchain/langgraph-sdk/auth");
> >
> > const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
> > const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE;
> >
> > // JWKS endpoint for Auth0
> > const JWKS = createRemoteJWKSet(
> >   new URL(`https://${AUTH0_DOMAIN}/.well-known/jwks.json`)
> > );
> >
> > // Create the Auth instance
> > const auth = new Auth();
> > // Register the authentication handler
> > auth.authenticate(async (request: Request) => {
> >   const authHeader = request.headers.get("Authorization");
> >   const xApiKeyHeader = request.headers.get("x-api-key");
> >     /**
> >      * LangGraph Platform will convert the `Authorization` header from the client to an `x-api-key` header automatically
> >      * as of now: https://docs.langchain.com/langgraph-platform/custom-auth
> >      *
> >      * We can still leverage the `Authorization` header when served in other infrastructure w/ langgraph-cli
> >      * or when running locally.
> >      */
> >     // This header is required in Langgraph Cloud.
> >     if (!authHeader && !xApiKeyHeader) {
> >       throw new HTTPException(401, {
> >         message: "Invalid auth header provided.",
> >       });
> >     }
> >
> >     // prefer the xApiKeyHeader first
> >     let token = xApiKeyHeader || authHeader;
> >
> >     // Remove "Bearer " prefix if present
> >     if (token && token.startsWith("Bearer ")) {
> >       token = token.substring(7);
> >     }
> >
> >     // Validate Auth0 Access Token using common JWKS endpoint
> >     if (!token) {
> >       throw new HTTPException(401, {
> >         message:
> >           "Authorization header format must be of the form: Bearer <token>",
> >       });
> >     }
> >
> >     if (token) {
> >       try {
> >         // Verify the JWT using Auth0 JWKS
> >         const { payload } = await jwtVerify(token, JWKS, {
> >           issuer: `https://${AUTH0_DOMAIN}/`,
> >           audience: AUTH0_AUDIENCE,
> >         });
> >
> >         console.log("✅ Auth0 JWT payload resolved!", payload);
> >
> >         // Return the verified payload - this becomes available in graph nodes
> >         return {
> >           identity: payload.sub!,
> >           email: payload.email as string,
> >           permissions:
> >             typeof payload.scope === "string" ? payload.scope.split(" ") : [],
> >           auth_type: "auth0",
> >           // include the access token for use with Auth0 Token Vault exchanges by tools
> >           getRawAccessToken: () => token,
> >           // Add any other claims you need
> >           ...payload,
> >         };
> >       } catch (jwtError) {
> >         console.log(
> >           "Auth0 JWT validation failed:",
> >           jwtError instanceof Error ? jwtError.message : "Unknown error"
> >         );
> >         throw new HTTPException(401, {
> >           message: "Invalid Authorization token provided.",
> >         });
> >       }
> >     }
> > });
> >
> > export { auth as authHandler };
> > ```
> >
> > ### Use access token to call APIs from a tool
> >
> > Once the user is authenticated, you can fetch an access token from Token Vault using the Auth0 AI SDK. In this example, we fetch an access token for a Google social connection.
> > Once you've obtained the access token for a connection, you can use it with an AI agent to fetch data during a tool call and provide contextual data in its response.
> > This example uses `GmailSearch` from the `@langchain/community` tools. This tool will use the access token provided by Token Vault to query for emails.
> >
> > ```ts src/lib/tools/gmail-seatch.ts wrap lines highlight={10} theme={null}
> > //...
> > import { getAccessTokenFromTokenVault } from "@auth0/ai-langchain";
> > import { GmailSearch } from "@langchain/community/tools/gmail";
> >
> > import { withGmailSearch } from "../../lib/auth0-ai";
> >
> > export const gmailSearchTool = withGmailSearch(
> >   new GmailSearch({
> >     credentials: {
> >       accessToken: async () => getAccessTokenFromTokenVault(),
> >     },
> >   })
> > );
> > ```
> >
> > Update your tool call to request an access token, as shown in the following example:
> >
> > ```ts src/lib/agent.ts wrap lines highlight={2,10-13} theme={null}
> > //...
> > import { gmailSearchTool } from './tools/gmail-search';
> >
> > //... existing code
> >
> > const tools = [gmailSearchTool];
> > //... existing code
> > export const agent = createReactAgent({
> >   llm,
> >   tools: new ToolNode(tools, {
> >     // Error handler must be disabled in order to trigger interruptions from within tools.
> >     handleToolErrors: false,
> >   }),
> >   // Modify the stock prompt in the prebuilt agent.
> >   prompt: AGENT_SYSTEM_TEMPLATE,
> >   store,
> >   checkpointer,
> > });
> > ```
> >
> > ### Add step-up authorization
> >
> > When you try to use the tool, the application requests any additional Google scopes that are required but not yet authorized. This process is called step-up authorization.
> >
> > To implement, install the Auth0 AI Components for Next.js SDK to get the required UI components:
> >
> > ```bash wrap lines theme={null}
> > npx @auth0/ai-components add TokenVault
> > ```
> >
> > Add a new file, `src/components/TokenVaultInterruptHandler.tsx`, with the following code:
> >
> > ```tsx src/components/TokenVaultInterruptHandler.tsx wrap lines theme={null}
> > import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
> > import type { Interrupt } from "@langchain/langgraph-sdk";
> >
> > import { TokenVaultConsent } from "@/components/auth0-ai/TokenVault";
> >
> > interface TokenVaultInterruptHandlerProps {
> >   interrupt: Interrupt | undefined | null;
> >   onFinish: () => void;
> > }
> >
> > export function TokenVaultInterruptHandler({
> >   interrupt,
> >   onFinish,
> > }: TokenVaultInterruptHandlerProps) {
> >   if (
> >     !interrupt ||
> >     !TokenVaultInterrupt.isInterrupt(interrupt.value)
> >   ) {
> >     return null;
> >   }
> >
> >   return (
> >     <div key={interrupt.ns?.join("")} className="whitespace-pre-wrap">
> >       <TokenVaultConsent
> >         mode="popup"
> >         interrupt={interrupt.value}
> >         onFinish={onFinish}
> >         connectWidget={{
> >           title: "Authorization Required.",
> >           description: interrupt.value.message,
> >           action: { label: "Authorize" },
> >         }}
> >       />
> >     </div>
> >   );
> > }
> > ```
> >
> > Now, update your chat window code to include the `TokenVaultInterruptHandler` component, for example:
> >
> > ```tsx src/components/chat-window.tsx wrap lines highlight={2,3,8-19,36-38} theme={null}
> > //...
> > import { TokenVaultInterruptHandler } from '@/components/TokenVaultInterruptHandler';
> >
> > //... existing code
> > export function ChatWindow(props: {
> >   //... existing code
> > }) {
> >   const [threadId, setThreadId] = useQueryState('threadId');
> >   const [input, setInput] = useState('');
> >   const chat = useStream({
> >     apiUrl: props.endpoint,
> >     assistantId: 'agent',
> >     threadId,
> >     onThreadId: setThreadId,
> >     onError: (e: any) => {
> >       console.error('Error: ', e);
> >       toast.error(`Error while processing your request`, { description: e.message });
> >     },
> >   });
> >   //... existing code
> >   return (
> >     <StickToBottom>
> >       <StickyToBottomContent
> >         className="absolute inset-0"
> >         contentClassName="py-8 px-2"
> >         content={
> >           chat.messages.length === 0 ? (
> >             <div>{props.emptyStateComponent}</div>
> >           ) : (
> >             <>
> >               <ChatMessages
> >                 aiEmoji={props.emoji}
> >                 messages={chat.messages}
> >                 emptyStateComponent={props.emptyStateComponent}
> >               />
> >               <div className="flex flex-col max-w-[768px] mx-auto pb-12 w-full">
> >                 <TokenVaultInterruptHandler interrupt={chat.interrupt} onFinish={() => chat.submit(null)} />
> >               </div>
> >             </>
> >           )
> >         }
> >         {/* ... existing code */}
> >       ></StickyToBottomContent>
> >     </StickToBottom>
> >   );
> > }
> > ```
> >
> > Now when step-up authorization is required, the user will see a prompt in the chat window asking them to authorize.
> >
> > ### Test your application
> >
> > Start your application. If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent to fetch emails from your Gmail account!
> >
> > That's it! You successfully integrated third-party API access using Token Vault into your app.
> >
> > ### View a complete example
> >
> > Want to see how it all comes together? Explore or clone the fully implemented sample application on [GitHub](https://github.com/auth0-samples/auth0-ai-samples/tree/main/call-apis-on-users-behalf/others-api/langchain-next-js).

> [tab] Vercel AI + Next.js
>
> Prerequisites
>
> Before getting started, make sure you have completed the following steps:
>
> > [tab] Use sample app (recommended)
> >
> > ### Download sample app
> >
> > Start by downloading and extracting the sample app. Then open in your preferred IDE.
> >
> > Download call-others-apis-on-users-behalf vercel-ai-next-js sample code
> >
> > ### Create your environment file
> >
> > In the root directory of your project, create an `.env.local` file and add the following content:
> >
> > ```bash .env.local wrap lines theme={null}
> > # Auth0 configuration
> > APP_BASE_URL='http://localhost:3000'
> > AUTH0_SECRET='random 32 byte value'
> > AUTH0_DOMAIN='<your-auth0-domain>'
> > AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
> > AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'
> >
> > # OpenAI API Key or any provider supported by the Vercel AI SDK
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to \*\*[Applications > Applications](https://manage.auth0.com/#/applications) \*\* in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:
> >
> > ```bash generate random 32 byte value theme={null}
> > openssl rand -hex 32
> > ```
> >
> > Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:
> >
> > ```bash .env.local wrap lines theme={null}
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > ### Install packages
> >
> > Ensure you have `npm` installed or follow the instructions here to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
> > In the root directory of your project, run the following command to install the required packages:
> >
> > ```bash wrap lines theme={null}
> > npm install
> > ```
> >
> > ### Test your application
> >
> > Start the application with `npm run dev`. Then, navigate to `http://localhost:3000`.
> >
> > If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent about your Google calendar.
> >
> > That's it! You successfully called a third-party API using Token Vault.
>
> > [tab] Integrate into your app
> >
> > ### Install packages
> >
> > In the root directory of your project, install the following packages:
> >
> > * `@auth0/ai-vercel`: [Auth0 AI SDK for Vercel AI](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-vercel) built for AI agents powered by the Vercel AI SDK.
> > * `ai`: Core [Vercel AI SDK](https://sdk.vercel.ai/docs) module that interacts with various AI model providers.
> > * `@ai-sdk/openai`: [OpenAI](https://sdk.vercel.ai/providers/ai-sdk-providers/openai) provider for the Vercel AI SDK.
> > * `@ai-sdk/react`: [React](https://react.dev/) UI components for the Vercel AI SDK.
> > * `zod`: TypeScript-first schema validation library.
> > * `googleapis`: Node.js client for Google APIs that supports authentication and authorization with OAuth 2.0.
> >
> > ```bash wrap lines theme={null}
> > npm install @auth0/ai-vercel@5 ai@6 @ai-sdk/openai@3 @ai-sdk/react@3 zod@3.25.76 googleapis@161
> > ```
> >
> > ### Update your environment file
> >
> > Add the following variables to your environment file:
> >
> > ```bash .env.local wrap lines theme={null}
> > # Auth0 configuration
> > APP_BASE_URL='http://localhost:3000'
> > AUTH0_SECRET='random 32 byte value'
> > AUTH0_DOMAIN='<your-auth0-domain>'
> > AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
> > AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'
> >
> > # OpenAI API Key or any provider supported by the Vercel AI SDK
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:
> >
> > ```bash generate random 32 byte value theme={null}
> > openssl rand -hex 32
> > ```
> >
> > Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:
> >
> > ### Set up Token Vault for Google social connection
> >
> > Use the [Auth0 AI SDK for Vercel AI](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-vercel) to get an access token for the [Google Social Connection](https://auth0.com/ai/docs/connections/google.md) using [Token Vault](https://auth0.com/docs/secure/call-apis-on-users-behalf/token-vault.md):
> >
> > * `connection`: pass in the name of the connection you want to access.
> > * `scopes`: pass in the scopes to be authorized for this connection.
> > * `refreshToken`: pass in the function to get the refresh token from the current session.
> >
> > Create a file at `src/lib/auth0-ai.ts` to instantiate the Auth0 AI SDK client:
> >
> > ```tsx src/lib/auth0-ai.ts wrap lines theme={null}
> > import { Auth0AI, getAccessTokenFromTokenVault } from "@auth0/ai-vercel";
> > import { getRefreshToken } from "./auth0";
> >
> > // Get the access token for a connection via Auth0
> > export const getAccessToken = async () => getAccessTokenFromTokenVault();
> >
> > const auth0AI = new Auth0AI();
> >
> > // Connection for Google services
> > export const withGoogleConnection = auth0AI.withTokenVault({
> >   connection: "google-oauth2",
> >   scopes: ["openid", "https://www.googleapis.com/auth/calendar.events"],
> >   refreshToken: getRefreshToken,
> > });
> > ```
> >
> > ### Pass credentials to the tools
> >
> > Create a file at `/src/lib/auth0.ts` file with the following code:
> >
> > ```tsx src/lib/auth0.ts wrap lines theme={null}
> > import { Auth0Client } from '@auth0/nextjs-auth0/server';
> >
> > // Create an Auth0 Client.
> > export const auth0 = new Auth0Client({
> >   enableConnectAccountEndpoint: true,
> > });
> >
> > // Get the refresh token from Auth0 session
> > export const getRefreshToken = async () => {
> >   const session = await auth0.getSession();
> >   return session?.tokenSet?.refreshToken;
> > };
> > ```
> >
> > #### Use access token to call APIs from a tool
> >
> > Once the user is authenticated, you can fetch an access token from the Token Vault using the Auth0 AI SDK. In this example, we fetch an access token for a Google social connection.
> > Once you've obtained the access token for a social connection, you can use it with an AI agent to fetch data during a tool call and provide contextual data in its response.
> >
> > In this example, we define a tool call, `getCalendarEventsTool`, that uses the access token with the Google Calendar API to query for calendar events on a specific date.
> > Update your tool call to request an access token similar to this example:
> >
> > ```ts src/lib/tools/google-calendar.ts wrap lines theme={null}
> > import { tool } from 'ai';
> > import { endOfDay, formatISO, startOfDay } from 'date-fns';
> > import { GaxiosError } from 'gaxios';
> > import { google } from 'googleapis';
> > import { z } from 'zod';
> > import { TokenVaultError } from '@auth0/ai/interrupts';
> >
> > import { getAccessToken, withGoogleConnection } from '../auth0-ai';
> >
> > export const getCalendarEventsTool = withGoogleConnection(
> >   tool({
> >     description: `Get calendar events for a given date from the user's Google Calendar`,
> >     inputSchema: z.object({
> >       date: z.coerce.date(),
> >     }),
> >     execute: async ({ date }) => {
> >       // Get the access token from Auth0 AI
> >       const accessToken = await getAccessToken();
> >
> >       // Google SDK
> >       try {
> >         const calendar = google.calendar('v3');
> >         const auth = new google.auth.OAuth2();
> >
> >         auth.setCredentials({
> >           access_token: accessToken,
> >         });
> >
> >         // Get events for the entire day
> >         const response = await calendar.events.list({
> >           auth,
> >           calendarId: 'primary',
> >           timeMin: formatISO(startOfDay(date)),
> >           timeMax: formatISO(endOfDay(date)),
> >           singleEvents: true,
> >           orderBy: 'startTime',
> >           maxResults: 50,
> >         });
> >
> >         const events = response.data.items || [];
> >
> >         return {
> >           date: formatISO(date, { representation: 'date' }),
> >           eventsCount: events.length,
> >           events: events.map((event) => ({
> >             id: event.id,
> >             summary: event.summary || 'No title',
> >             description: event.description,
> >             startTime: event.start?.dateTime || event.start?.date,
> >             endTime: event.end?.dateTime || event.end?.date,
> >             location: event.location,
> >             attendees:
> >               event.attendees?.map((attendee) => ({
> >                 email: attendee.email,
> >                 name: attendee.displayName,
> >                 responseStatus: attendee.responseStatus,
> >               })) || [],
> >             status: event.status,
> >             htmlLink: event.htmlLink,
> >           })),
> >         };
> >       } catch (error) {
> >         if (error instanceof GaxiosError) {
> >           if (error.status === 401) {
> >             throw new TokenVaultError(`Authorization required to access the Token Vault connection`);
> >           }
> >         }
> >
> >         throw error;
> >       }
> >     },
> >   }),
> > );
> > ```
> >
> > ### Add step up authorization
> >
> > When you try to use the tool, the application requests any additional Google scopes that are required but not yet authorized. This process is called step-up authorization.
> >
> > To implement, install the Auth0 AI Components for Next.js SDK to get the required UI components:
> >
> > ```bash wrap lines theme={null}
> > npx @auth0/ai-components add TokenVault
> > ```
> >
> > Add a new file, `src/components/TokenVaultInterruptHandler.tsx` with the following code:
> >
> > ```tsx src/components/TokenVaultInterruptHandler.tsx wrap lines theme={null}
> > import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
> > import type { Auth0InterruptionUI } from "@auth0/ai-vercel/react";
> >
> > import { TokenVaultConsent } from "@/components/auth0-ai/TokenVault";
> >
> > interface TokenVaultInterruptHandlerProps {
> >   interrupt: Auth0InterruptionUI | null;
> >   onFinish?: () => void;
> > }
> >
> > export function TokenVaultInterruptHandler({
> >   interrupt,
> >   onFinish,
> > }: TokenVaultInterruptHandlerProps) {
> >   if (!TokenVaultInterrupt.isInterrupt(interrupt)) {
> >     return null;
> >   }
> >
> >   return (
> >     <div key={interrupt.name} className="whitespace-pre-wrap">
> >       <TokenVaultConsent
> >         mode="popup"
> >         interrupt={interrupt}
> >         onFinish={onFinish}
> >         connectWidget={{
> >           title: "Authorization Required.",
> >           description: interrupt.message,
> >           action: { label: "Authorize" },
> >         }}
> >       />
> >     </div>
> >   );
> > }
> > ```
> >
> > Now, update your chat window code to include the `TokenVaultInterruptHandler` component, for example:
> >
> > ```tsx src/components/chat-window.tsx wrap lines highlight={2,3,10-18,36-38} theme={null}
> > //...
> > import { useInterruptions } from '@auth0/ai-vercel/react';
> > import { TokenVaultInterruptHandler } from '@/components/TokenVaultInterruptHandler';
> >
> >
> > //... existing code
> > export function ChatWindow(props: {
> >   //... existing code
> > }) {
> >   const chat = useInterruptions((handler) =>
> >     useChat({
> >       api: props.endpoint,
> >       onError: handler((e: Error) => {
> >         console.error('Error: ', e);
> >         toast.error(`Error while processing your request`, { description: e.message });
> >       }),
> >     }),
> >   );
> >
> >   //... existing code
> >   return (
> >     <StickToBottom>
> >       <StickyToBottomContent
> >         className="absolute inset-0"
> >         contentClassName="py-8 px-2"
> >         content={
> >           chat.messages.length === 0 ? (
> >             <div>{props.emptyStateComponent}</div>
> >           ) : (
> >             <>
> >               <ChatMessages
> >                 aiEmoji={props.emoji}
> >                 messages={chat.messages}
> >                 emptyStateComponent={props.emptyStateComponent}
> >               />
> >               <div className="flex flex-col max-w-[768px] mx-auto pb-12 w-full">
> >                 <TokenVaultInterruptHandler interrupt={chat.toolInterrupt} />
> >               </div>
> >             </>
> >           )
> >         }
> >         {/* ... existing code */}
> >       ></StickyToBottomContent>
> >     </StickToBottom>
> >   );
> > }
> > ```
> >
> > Now when step-up authorization is required, the user will see a prompt in the chat window asking them to authorize.
> >
> > ### Add the tool to the AI agent
> >
> > Handle the interrupts from the AI agent and call the tool from your AI app to get calendar events. Update your chat route, typically at `/src/app/api/chat/route.ts`, as shown in the following example:
> >
> > ```ts src/app/api/chat/route.ts wrap lines theme={null}
> > //...
> > import { setAIContext } from "@auth0/ai-vercel";
> > import {
> >   errorSerializer,
> >   withInterruptions,
> > } from "@auth0/ai-vercel/interrupts";
> > import { getCalendarEventsTool } from "@/lib/tools/google-calendar";
> > //... existing code
> >
> > export async function POST(req: NextRequest) {
> >   const request = await req.json();
> >   const messages = sanitizeMessages(request.messages);
> >
> >   setAIContext({ threadID: request.id });
> >
> >   const tools = {
> >     getCalendarEventsTool,
> >   };
> >
> >    const modelMessages = await convertToModelMessages(messages);
> >
> >   const stream = createUIMessageStream({
> >     originalMessages: messages,
> >     execute: withInterruptions(
> >       async ({ writer }) => {
> >         const result = streamText({
> >           model: openai('gpt-4o-mini'),
> >           system: AGENT_SYSTEM_TEMPLATE,
> >           messages: modelMessages,
> >           tools,
> >
> >           onFinish: (output) => {
> >             if (output.finishReason === 'tool-calls') {
> >               const lastMessage = output.content[output.content.length - 1];
> >               if (lastMessage?.type === 'tool-error') {
> >                 const { toolName, toolCallId, error, input } = lastMessage;
> >                 const serializableError = {
> >                   cause: error,
> >                   toolCallId: toolCallId,
> >                   toolName: toolName,
> >                   toolArgs: input,
> >                 };
> >
> >                 throw serializableError;
> >               }
> >             }
> >           },
> >         });
> >         writer.merge(
> >           result.toUIMessageStream({
> >             sendReasoning: true,
> >           }),
> >         );
> >       },
> >       {
> >         messages: messages,
> >         tools,
> >       },
> >     ),
> >     onError: errorSerializer((err) => {
> >       console.error('ai-sdk route: stream error', err);
> >       return 'Oops, an error occured!';
> >     }),
> >   });
> >
> >   return createUIMessageStreamResponse({ stream });
> > }
> > ```
> >
> > ### Test your application
> >
> > Start your application. If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent about your Google calendar!
> >
> > That's it! You successfully integrated third-party API access using Token Vault into your app.
> >
> > ### View a complete example
> >
> > Want to see how it all comes together? Explore or clone the fully implemented sample application [here on GitHub](https://github.com/auth0-samples/auth0-ai-samples/tree/main/call-apis-on-users-behalf/others-api/vercel-ai-next-js).

> [tab] LangGraph + FastAPI
>
> Prerequisites
>
> Before getting started, make sure you have completed the following steps:
>
> > [tab] Use sample app (recommended)
> >
> > ### Download sample app
> >
> > Start by downloading and extracting the sample app. Then open in your preferred IDE.
> >
> > Download call-others-apis-on-users-behalf langchain-fastapi-py sample code
> >
> > The project is divided into two parts:
> >
> > * `backend/`: contains the backend code for the web app, an API written in Python using FastAPI and the LangGraph agent.
> > * `frontend/`: contains the frontend code for the web app, written in React as a Vite SPA.
> >
> > ### Install backend packages
> >
> > In the `backend` directory of your project, install the required packages using your preferred package manager, such as `uv`. You can find the instructions on how to [install uv](https://docs.astral.sh/uv/getting-started/installation/) in its documentation.
> >
> > ```bash wrap lines theme={null}
> > cd backend
> > uv sync
> > ```
> >
> > ### Create your environment file
> >
> > In the `backend` directory of your project, create a new file and name it `.env` and add the following content:
> >
> > ```bash .env wrap lines theme={null}
> > # Auth0 configuration
> > APP_BASE_URL='http://localhost:5173'
> > AUTH0_SECRET='random 32 byte value'
> > AUTH0_DOMAIN='<your-auth0-domain>'
> > AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
> > AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'
> >
> > # OpenAI API Key or any provider supported by the LangGraph SDK
> > OPENAI_API_KEY="YOUR_API_KEY"
> >
> > # LANGGRAPH
> > LANGGRAPH_API_URL=http://localhost:54367
> > ```
> >
> > To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:
> >
> > ```bash generate random 32 byte value theme={null}
> > openssl rand -hex 32
> > ```
> >
> > Lastly, set your OpenAI API key or use any provider supported by the LangGraph SDK:
> >
> > ```bash .env wrap lines theme={null}
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > In the `frontend` directory of your project, copy the `.env.example` file to `.env`. No further customization required.
> >
> > ### Install frontend packages
> >
> > Ensure you have `npm` installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation.
> > Navigate to the `frontend` directory of your project and install the required packages:
> >
> > ```bash wrap lines theme={null}
> > cd frontend
> > npm install
> > ```
> >
> > ### Run your application
> >
> > To run your application, start the FastAPI backend, LangGraph server, and the frontend in three terminals:
> >
> > 1. In one terminal, start the FastAPI backend:
> >
> > ```bash wrap lines theme={null}
> > cd backend
> > source .venv/bin/activate
> > uv pip install auth0_fastapi
> > fastapi dev app/main.py
> > ```
> >
> > 2. In a second new terminal, start the LangGraph server:
> >
> > ```bash wrap lines theme={null}
> > cd backend
> > source .venv/bin/activate
> > uv pip install -U langgraph-api
> > langgraph dev --port 54367 --allow-blocking
> > ```
> >
> > > [!NOTE]
> > >
> > > This opens LangGraph Studio in a new tab. You can safely close it.
> >
> > 3. In a third new terminal, start the frontend:
> >
> > ```bash wrap lines theme={null}
> > cd frontend
> > npm run dev
> > ```
> >
> > Visit the URL `http://localhost:5173` in your browser. If you are already logged in, make sure to log out and log back in using Google. Now ask your AI agent to list the upcoming events in your Google Calendar!
> >
> > That's it! You successfully called a third-party API using Token Vault.
>
> > [tab] Integrate into your app
> >
> > ### Install packages
> >
> > In the `backend` directory of your project, install the following dependencies:
> >
> > * `auth0-ai-langchain`: [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-python/tree/main/packages/auth0-ai-langchain) built for AI agents powered by LangChain.
> > * `langgraph`: [LangGraph](https://pypi.org/project/langgraph/) for building stateful, multi-actor applications with LLMs.
> > * `langchain-openai`: LangChain integrations for OpenAI.
> > * `langgraph-cli`: LangGraph CLI for running a local LangGraph server.
> > * `google-api-python-client`: Google API client library for Python.
> >
> > Make sure you have [uv](https://docs.astral.sh/uv/) installed and run the following command to install the dependencies:
> >
> > ```bash wrap lines theme={null}
> > cd backend
> > uv sync
> > uv add "auth0-ai-langchain>=1.0.1" "langgraph>=0.5.4" langchain-openai "langgraph-cli[inmem]>=0.3.6" google-api-python-client --prerelease=allow
> > ```
> >
> > ### Update your environment file
> >
> > In the `backend` directory of your project, add the following variables to your environment file:
> >
> > ```bash .env wrap lines theme={null}
> > # Auth0 configuration
> > APP_BASE_URL='http://localhost:5173'
> > AUTH0_SECRET='random 32 byte value'
> > AUTH0_DOMAIN='<your-auth0-domain>'
> > AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
> > AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'
> >
> > # OpenAI API Key or any provider supported by the Vercel AI SDK
> > OPENAI_API_KEY="YOUR_API_KEY"
> >
> > # LANGGRAPH
> > LANGGRAPH_API_URL=http://localhost:54367
> > ```
> >
> > To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field.
> >
> > ```bash generate random 32 byte value theme={null}
> > openssl rand -hex 32
> > ```
> >
> > Lastly, set your OpenAI API key or use any provider supported by the LangGraph SDK.
> >
> > ### Set up Token Vault for Google social connection
> >
> > Use the [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-langchain) to get an access token for the [Google Social Connection](https://auth0.com/ai/docs/connections/google.md) using [Token Vault](https://auth0.com/docs/secure/call-apis-on-users-behalf/token-vault.md):
> >
> > * `connection`: pass in the name of the connection you want to access.
> > * `scopes`: pass in the scopes to be authorized for this connection.
> >
> > Create a file at `app/core/auth0_ai.py` to instantiate the Auth0 AI SDK client:
> >
> > ```python app/core/auth0_ai.py wrap lines theme={null}
> > from auth0_ai.authorizers.types import Auth0ClientParams
> > from auth0_ai_langchain.auth0_ai import Auth0AI
> >
> > from app.core.config import settings
> >
> > auth0_ai = Auth0AI(
> >     Auth0ClientParams(
> >         {
> >             "domain": settings.AUTH0_DOMAIN,
> >             "client_id": settings.AUTH0_CLIENT_ID,
> >             "client_secret": settings.AUTH0_CLIENT_SECRET,
> >         }
> >     )
> > )
> >
> > with_calendar_access = auth0_ai.with_token_vault(
> >     connection="google-oauth2",
> >     scopes=["openid", "https://www.googleapis.com/auth/calendar.events"],
> >     # Optional: authorization_params={"login_hint": "user@example.com", "ui_locales": "en"}
> > )
> > ```
> >
> > ### Pass credentials to the AI agent
> >
> > Update your API route to pass the user session data and an external provider's access token to the AI agent, for example, in `app/api/routes/chat.py`:
> >
> > ```python app/api/routes/chat.py wrap lines highlight={2,9,21} theme={null}
> > # ...
> > from app.core.auth import auth_client
> > # ...
> >
> > @agent_router.api_route(
> >     "/{full_path:path}", methods=["GET", "POST", "DELETE", "PATCH", "PUT", "OPTIONS"]
> > )
> > async def api_route(
> >     request: Request, full_path: str, auth_session=Depends(auth_client.require_session)
> > ):
> >     try:
> >         # ... existing code
> >
> >         # Prepare body
> >         body = await request.body()
> >         if request.method in ("POST", "PUT", "PATCH") and body:
> >             content = await request.json()
> >             content["config"] = {
> >                 "configurable": {
> >                     "_credentials": {
> >                         "refresh_token": auth_session.get("refresh_token"),
> >                     }
> >                 }
> >             }
> >             body = json.dumps(content).encode("utf-8")
> >
> >             # ... existing code
> > ```
> >
> > ### Use access token to call APIs from a tool
> >
> > Once the user is authenticated, you can fetch an access token from Token Vault using the Auth0 AI SDK. In this example, we fetch an access token for a Google social connection.
> > Once you've obtained the access token for a connection, you can use it with an AI agent to fetch data during a tool call and provide contextual data in its response.
> >
> > Create or update a tool similar to this example that creates a Google Calendar query tool:
> >
> > ```python app/agents/tools/google_calendar.py wrap lines theme={null}
> > from langchain_core.tools import StructuredTool
> > from google.oauth2.credentials import Credentials
> > from googleapiclient.discovery import build
> > from pydantic import BaseModel
> > from auth0_ai_langchain.token_vault import (
> >     get_access_token_from_token_vault,
> > )
> > import datetime
> > import json
> >
> > from app.core.auth0_ai import with_calendar_access
> >
> > async def list_upcoming_events_fn():
> >     """List upcoming events from the user's Google Calendar"""
> >     google_access_token = get_access_token_from_token_vault()
> >     if not google_access_token:
> >         raise ValueError(
> >             "Authorization required to access the Google Calendar API"
> >         )
> >
> >     calendar_service = build(
> >         "calendar",
> >         "v3",
> >         credentials=Credentials(google_access_token),
> >     )
> >
> >     events = (
> >         calendar_service.events()
> >         .list(
> >             calendarId="primary",
> >             timeMin=datetime.datetime.now().isoformat() + "Z",
> >             timeMax=(datetime.datetime.now() + datetime.timedelta(days=7)).isoformat()
> >             + "Z",
> >             maxResults=5,
> >             singleEvents=True,
> >             orderBy="startTime",
> >         )
> >         .execute()
> >         .get("items", [])
> >     )
> >
> >     return json.dumps(
> >         [
> >             {
> >                 "summary": event["summary"],
> >                 "start": event["start"].get("dateTime", event["start"].get("date")),
> >             }
> >             for event in events
> >         ]
> >     )
> >
> > list_upcoming_events = with_calendar_access(
> >     StructuredTool(
> >         name="list_upcoming_events",
> >         description="List upcoming events from the user's Google Calendar",
> >         args_schema=BaseModel,
> >         coroutine=list_upcoming_events_fn,
> >     )
> > )
> > ```
> >
> > ### Add the tool to the AI agent
> >
> > If you created a new tool, ensure it's added to the list of tools used by your AI agent. For example:
> >
> > ```python app/agents/assistant0.py wrap lines highlight={2,4} theme={null}
> > # ...
> > from app.agents.tools.google_calendar import list_upcoming_events
> >
> > tools = [list_upcoming_events]
> >
> > llm = ChatOpenAI(model="gpt-4.1-mini")
> >
> > # ... existing code
> > agent = create_react_agent(
> >     llm,
> >     tools=ToolNode(tools, handle_tool_errors=False),
> >     prompt=get_prompt(),
> > )
> > ```
> >
> > ### Add step-up authorization
> >
> > When you try to use the tool, the application requests any additional Google scopes that are required but not yet authorized. This process is called step-up authorization.
> >
> > To implement, install the Auth0 AI Components for React SDK to get the required UI components:
> >
> > ```bash wrap lines theme={null}
> > cd frontend
> > npm install @auth0/ai @langchain/langgraph-sdk
> > npx @auth0/ai-components add TokenVault
> > ```
> >
> > Add a new file, `src/components/TokenVaultInterruptHandler.tsx`, with the following code:
> >
> > ```tsx src/components/TokenVaultInterruptHandler.tsx wrap lines theme={null}
> > import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
> > import type { Interrupt } from "@langchain/langgraph-sdk";
> >
> > import { TokenVaultConsent } from "@/components/auth0-ai/TokenVault";
> > import type { TokenVaultAuthProps } from "./auth0-ai/TokenVault/TokenVaultAuthProps";
> >
> > export interface TokenVaultInterruptHandlerProps {
> >   interrupt: Interrupt | undefined | null;
> >   onFinish: () => void;
> >   auth?: TokenVaultAuthProps['auth'];
> > }
> >
> > export function TokenVaultInterruptHandler({
> >   interrupt,
> >   onFinish,
> >   auth,
> > }: TokenVaultInterruptHandlerProps) {
> >   if (!interrupt) {
> >     return null;
> >   }
> >
> >   if (!TokenVaultInterrupt.isInterrupt(interrupt.value)) {
> >     return null;
> >   }
> >
> >   return (
> >     <div key={interrupt.ns?.join("")} className="whitespace-pre-wrap">
> >       <TokenVaultConsent
> >         mode="popup"
> >         interrupt={interrupt.value}
> >         onFinish={onFinish}
> >         auth={auth}
> >         connectWidget={{
> >           title: "Authorization Required.",
> >           description: interrupt.value.message,
> >           action: { label: "Authorize" },
> >         }}
> >       />
> >     </div>
> >   );
> > }
> > ```
> >
> > Now, update your chat window code to include the `TokenVaultInterruptHandler` component, for example:
> >
> > ```tsx src/components/chat-window.tsx wrap lines highlight={2,3,25-50} theme={null}
> > //...
> > import { TokenVaultInterruptHandler } from '@/components/TokenVaultInterruptHandler';
> > import { getConnectUrl } from "@/lib/use-auth";
> >
> > //... existing code
> > export function ChatWindow(props: {
> >   //... existing code
> > }) {
> >   //... existing code
> >   return (
> >     <StickToBottom>
> >       <StickyToBottomContent
> >         className="absolute inset-0"
> >         contentClassName="py-8 px-2"
> >         content={
> >           chat.messages.length === 0 ? (
> >             <div>{props.emptyStateComponent}</div>
> >           ) : (
> >             <>
> >               <ChatMessages
> >                 aiEmoji={props.emoji}
> >                 messages={chat.messages}
> >                 emptyStateComponent={props.emptyStateComponent}
> >               />
> >               <div className="flex flex-col max-w-[768px] mx-auto pb-12 w-full">
> >                 {!!chat.interrupt?.value && (
> >                   <TokenVaultInterruptHandler
> >                     auth={{
> >                       connectPath: getConnectUrl(),
> >                       returnTo: new URL(
> >                         "/close",
> >                         window.location.origin
> >                       ).toString(),
> >                     }}
> >                     interrupt={{
> >                       ...chat.interrupt,
> >                       value: {
> >                         ...chat.interrupt.value,
> >                         requiredScopes:
> >                           (
> >                             chat.interrupt.value as {
> >                               required_scopes: [string];
> >                             }
> >                           ).required_scopes || [],
> >                         authorizationParams:
> >                           (
> >                             chat.interrupt.value as {
> >                               authorization_params: Record<string, string>;
> >                             }
> >                           ).authorization_params || {},
> >                       },
> >                     }}
> >                     onFinish={() => chat.submit(null)}
> >                   />
> >                 )}
> >               </div>
> >             </>
> >           )
> >         }
> >         {/* ... existing code */}
> >       ></StickyToBottomContent>
> >     </StickToBottom>
> >   );
> > }
> > ```
> >
> > Now when step-up authorization is required, the user will see a prompt in the chat window asking them to authorize.
> >
> > ### Test your application
> >
> > Start your application. If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent to list the upcoming events in your Google Calendar!
> >
> > That's it! You successfully integrated third-party API access using Token Vault into your app.
> >
> > ### View a complete example
> >
> > Want to see how it all comes together? Explore or clone the fully implemented sample application on [GitHub](https://github.com/auth0-samples/auth0-ai-samples/tree/main/call-apis-on-users-behalf/others-api/langchain-fastapi-py).

> [tab] Vercel AI + React SPA
>
> Prerequisites
>
> Before getting started, make sure you have completed the following steps:
>
> ### Key differences from Next.js approach
>
> This React SPA implementation differs from the [Next.js example](https://auth0.com/ai/docs/get-started/call-third-party-apis-on-users-behalf.md) in a few important ways:
>
> 1. **Token Vault Access Token Exchange**: Instead of using refresh tokens, the React SPA implementation exchanges the SPA's access token for a third-party access token.
>
> 2. **Client-Side Authorization**: Client login and step-up authorization are handled client-side using `@auth0/auth0-spa-js`.
>
> 3. **Resource Server Client**: Requires a special Resource Server Client configured for token exchange with Token Vault.
>
> 4. **Interrupt Handling**: The React client manages tool access errors and step-up authorization using interrupts that trigger a redirect for re-authorization.
>
> > [tab] Use sample app (recommended)
> >
> > ### Download sample app
> >
> > Start by downloading and extracting the sample app. Then open in your preferred IDE.
> >
> > Download call-others-apis-on-users-behalf langchain-react-spa-js sample code
> >
> > ### Create your environment files
> >
> > Add separate `.env` files with environment variables for the client and server.
> >
> > #### Client (client/.env)
> >
> > ```bash .env wrap lines theme={null}
> > VITE_AUTH0_DOMAIN=your-auth0-domain
> > VITE_AUTH0_CLIENT_ID=your-spa-client-id
> > VITE_AUTH0_AUDIENCE=your-api-identifier
> > VITE_API_URL=http://localhost:3001
> > ```
> >
> > To get your SPA application's `VITE_AUTH0_DOMAIN`, and `VITE_AUTH0_CLIENT_ID`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your SPA application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > To get your API's `AUTH0_AUDIENCE`, navigate to **[Applications > APIs](https://manage.auth0.com/#/apis)** in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.
> >
> > #### Server (server/.env)
> >
> > ```bash .env wrap lines theme={null}
> > AUTH0_DOMAIN=your-auth0-domain
> > AUTH0_AUDIENCE=your-api-identifier
> > AUTH0_CUSTOM_API_CLIENT_ID=your-custom-api-client-id
> > AUTH0_CUSTOM_API_CLIENT_SECRET=your-custom-api-client-secret
> > OPENAI_API_KEY=your-openai-api-key
> > PORT=3001
> > ```
> >
> > To get your Custom API Client's `AUTH0_DOMAIN`, `AUTH0_AUDIENCE`, and `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > The `AUTH0_AUDIENCE` should match the identifier of the API referenced in the `client/.env` file above.
> >
> > Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:
> >
> > ```bash .env.local wrap lines theme={null}
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > ### Install packages
> >
> > Ensure you have `npm` installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation.
> > In the root directory of your project, run the following command to install the required packages:
> >
> > ```bash wrap lines theme={null}
> > npm install
> > ```
> >
> > ### Test your application
> >
> > 1. Start the client and server using Turbo: `npm run dev`.
> > 2. Navigate to `http://localhost:5173`.
> > 3. Log in with Google and ask your AI agent about your calendar.
> >
> > The application will automatically prompt for additional calendar permissions when needed using Auth0's step-up authorization flow.
> >
> > That's it! You've successfully called a third-party API using Token Vault in your React SPA + Vercel AI application.
>
> > [tab] Integrate into your app
> >
> > ### Install dependencies
> >
> > In the root directory of your project, ensure you have the following client and server dependencies:
> >
> > **Client dependencies:**
> >
> > * `@auth0/auth0-spa-js`: Auth0 SPA SDK for client-side authentication
> > * `@auth0/ai-vercel`: [Auth0 AI SDK for Vercel AI](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-vercel) built for AI agents
> > * `ai`: Core [Vercel AI SDK](https://sdk.vercel.ai/docs) module
> >
> > **Server dependencies:**
> >
> > * `@hono/node-server`: Node.js server adapter for Hono
> > * `hono`: Lightweight web framework
> > * `ai`: Core [Vercel AI SDK](https://sdk.vercel.ai/docs) module
> > * `@ai-sdk/openai`: [OpenAI](https://sdk.vercel.ai/providers/ai-sdk-providers/openai) provider
> > * `googleapis`: Node.js client for Google APIs
> > * `jose`: JavaScript Object Signing and Encryption library for JWT verification
> >   To install all the client and server dependencies, navigate to the root directory of your project and run the following command:
> >
> > ```bash wrap lines theme={null}
> > # Install all client & server dependencies from the root directory of the project.
> > npm install
> > ```
> >
> > ### Update the environment files
> >
> > Add separate `.env` files with environment variables for the client and server.
> >
> > #### Client (client/.env)
> >
> > ```bash .env wrap lines theme={null}
> > VITE_AUTH0_DOMAIN=your-auth0-domain
> > VITE_AUTH0_CLIENT_ID=your-spa-client-id
> > VITE_AUTH0_AUDIENCE=your-api-identifier
> > VITE_API_URL=http://localhost:3001
> > ```
> >
> > To get your SPA application's `VITE_AUTH0_DOMAIN`, and `VITE_AUTH0_CLIENT_ID`, navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your SPA application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > To get your API's `AUTH0_AUDIENCE`, navigate to **[Applications > APIs](https://manage.auth0.com/#/apis)** in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.
> >
> > #### Server (server/.env)
> >
> > ```bash .env wrap lines theme={null}
> > AUTH0_DOMAIN=your-auth0-domain
> > AUTH0_AUDIENCE=your-api-identifier
> > AUTH0_CUSTOM_API_CLIENT_ID=your-custom-api-client-id
> > AUTH0_CUSTOM_API_CLIENT_SECRET=your-custom-api-client-secret
> > OPENAI_API_KEY=your-openai-api-key
> > PORT=3001
> > ```
> >
> > To get your Custom API Client's `AUTH0_DOMAIN`, `AUTH0_AUDIENCE`, and `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to **[Applications > Applications](https://manage.auth0.com/#/applications)** in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
> > Copy each value to the matching setting.
> >
> > The `AUTH0_AUDIENCE` should match the identifier of the API referenced in the `client/.env` file above.
> >
> > Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:
> >
> > ```bash .env.local wrap lines theme={null}
> > OPENAI_API_KEY="YOUR_API_KEY"
> > ```
> >
> > ### Create an Auth0 Provider and initialize the Auth0 SPA SDK
> >
> > Create `client/src/lib/auth0.ts` and initialize the Auth0 SPA SDK with your Auth0 application details, along with configuration for token storage and refresh tokens:
> >
> > ```ts client/src/lib/auth0.ts wrap lines theme={null}
> > import { Auth0Client, createAuth0Client } from "@auth0/auth0-spa-js";
> >
> > // Auth0 configuration
> > const AUTH0_DOMAIN = import.meta.env.VITE_AUTH0_DOMAIN;
> > const AUTH0_CLIENT_ID = import.meta.env.VITE_AUTH0_CLIENT_ID;
> > const AUTH0_AUDIENCE = import.meta.env.VITE_AUTH0_AUDIENCE;
> >
> > export const initAuth0 = async (): Promise<Auth0Client> => {
> >   if (auth0Client) {
> >     return auth0Client;
> >   }
> >
> >   auth0Client = await createAuth0Client({
> >     domain: AUTH0_DOMAIN,
> >     clientId: AUTH0_CLIENT_ID,
> >     authorizationParams: {
> >       redirect_uri: window.location.origin,
> >       audience: AUTH0_AUDIENCE,
> >       scope: "openid profile email offline_access",
> >     },
> >     // Store tokens in localstorage to allow restoring the user session after following redirects.
> >     // Redirects are necessary to connect an account for the user.
> >     cacheLocation: 'localstorage',
> >     useRefreshTokens: true,
> >     useMrrt: true,
> >     useDpop: true,
> >   });
> >
> >   return auth0Client;
> > };
> > ```
> >
> > Create a Provider component to wrap your application with Auth0 context in `client/src/components/Auth0Provider.tsx`:
> >
> > ```tsx client/src/components/Auth0Provider.tsx wrap lines theme={null}
> > import React, { ReactNode, useEffect, useRef, useState } from "react";
> >
> > import { User } from "@auth0/auth0-ai-js-examples-react-hono-ai-sdk-shared";
> >
> > import {
> >   getToken,
> >   getUser,
> >   initAuth0,
> >   isAuthenticated,
> >   login,
> >   logout,
> > } from "../lib/auth0";
> > import { Auth0Context, Auth0ContextType } from "./auth0-context";
> >
> > interface Auth0ProviderProps {
> >   children: ReactNode;
> > }
> >
> > export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
> >   const [isLoading, setIsLoading] = useState(true);
> >   const [isAuthenticatedState, setIsAuthenticatedState] = useState(false);
> >   const [user, setUser] = useState<User | null>(null);
> >   const [error, setError] = useState<string | null>(null);
> >   const initRef = useRef(false);
> >
> >   useEffect(() => {
> >     // Prevent double execution in React Strict Mode
> >     if (initRef.current) return;
> >     initRef.current = true;
> >
> >     const initializeAuth0 = async () => {
> >       try {
> >         setIsLoading(true);
> >         const client = await initAuth0();
> >
> >         // Check if user is returning from login redirect or connect flow
> >         if (
> >           (window.location.search.includes("code=") ||
> >             window.location.search.includes("connect_code=")) &&
> >           window.location.search.includes("state=")
> >         ) {
> >           await client.handleRedirectCallback();
> >           window.history.replaceState({}, document.title, window.location.pathname);
> >         }
> >
> >         const authenticated = await isAuthenticated();
> >         setIsAuthenticatedState(authenticated);
> >
> >         if (authenticated) {
> >           const userData = await getUser();
> >           setUser(userData as User);
> >         }
> >       } catch (err) {
> >         setError(err instanceof Error ? err.message : "Authentication error");
> >       } finally {
> >         setIsLoading(false);
> >       }
> >     };
> >
> >     initializeAuth0();
> >   }, []);
> >
> >   const handleLogin = async (targetUrl?: string) => {
> >     try {
> >       setError(null);
> >       await login(targetUrl);
> >     } catch (err) {
> >       setError(err instanceof Error ? err.message : "Login failed");
> >     }
> >   };
> >
> >   const handleLogout = async () => {
> >     try {
> >       setError(null);
> >       await logout();
> >       setIsAuthenticatedState(false);
> >       setUser(null);
> >     } catch (err) {
> >       setError(err instanceof Error ? err.message : "Logout failed");
> >     }
> >   };
> >
> >   const contextValue: Auth0ContextType = {
> >     isLoading,
> >     isAuthenticated: isAuthenticatedState,
> >     user,
> >     error,
> >     login: handleLogin,
> >     logout: handleLogout,
> >     getToken,
> >   };
> >
> >   return (
> >     <Auth0Context.Provider value={contextValue}>
> >       {children}
> >     </Auth0Context.Provider>
> >   );
> > };
> > ```
> >
> > Finally, wrap your application with the `Auth0Provider` in `client/src/main.tsx`:
> >
> > ```tsx client/src/main.tsx wrap lines theme={null}
> > import "./index.css";
> >
> > import { StrictMode } from "react";
> > import { createRoot } from "react-dom/client";
> >
> > import App from "./App.tsx";
> > import { Auth0Provider } from "./contexts/Auth0Context.tsx";
> >
> > createRoot(document.getElementById("root")!).render(
> >   <StrictMode>
> >     <Auth0Provider>
> >       <App />
> >     </Auth0Provider>
> >   </StrictMode>
> > );
> > ```
> >
> > ### Configure the SPA for step-up authorization
> >
> > Unlike the [Next.js example](https://auth0.com/ai/docs/get-started/call-third-party-apis-on-users-behalf.md), which uses refresh tokens, this React SPA approach uses **access tokens** for token exchange with Token Vault. The SPA handles step-up authorization using the `loginWithPopup()` method from the Auth0 SPA SDK to display the consent screen and allow the user to grant additional permissions.
> >
> > Create `client/src/components/TokenVaultConsentPopup.tsx`:
> >
> > ```tsx client/src/components/TokenVaultConsentPopup.tsx wrap lines theme={null}
> > import { useCallback, useState } from "react";
> > import type { Auth0InterruptionUI } from "@auth0/ai-vercel/react";
> >
> > import { getAuth0Client } from "../lib/auth0";
> > import { Button } from "./ui/button";
> > import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
> >
> > /**
> >  * Component for handling connection authorization popups.
> >  * This component manages the connect account flow for Token Vault, allowing the
> >  * user to authorize access to third-party providers.
> >  */
> >
> > interface TokenVaultConsentPopupProps {
> >   interrupt: Auth0InterruptionUI;
> > }
> >
> > export function TokenVaultConsentPopup({
> >   interrupt,
> > }: TokenVaultConsentPopupProps) {
> >   const [isLoading, setIsLoading] = useState(false);
> >
> >   const { connection, requiredScopes, authorizationParams, resume } = interrupt;
> >
> >   // Use Auth0 SPA SDK to connect an external account
> >   const startConnectAccountFlow = useCallback(async () => {
> >     try {
> >       setIsLoading(true);
> >
> >       // Filter out empty scopes
> >       const validScopes = requiredScopes.filter(
> >         (scope: string) => scope && scope.trim() !== "",
> >       );
> >
> >       const auth0Client = getAuth0Client();
> >
> >       // Use the connect account flow to request authorization+consent for the external API.
> >       // This will redirect the browser away from the SPA.
> >       await auth0Client.connectAccountWithRedirect({
> >         connection,
> >         scopes: validScopes,
> >         ...(authorizationParams
> >           ? { authorization_params: authorizationParams }
> >           : {}),
> >       });
> >
> >       setIsLoading(false);
> >
> >       // Resume the interrupted tool after successful authorization
> >       if (typeof resume === "function") {
> >         resume();
> >       }
> >     } catch (error) {
> >       console.error("Connect account flow failed:", error);
> >       setIsLoading(false);
> >
> >       // Even if login fails, we should clear the interrupt
> >       if (typeof resume === "function") {
> >         resume();
> >       }
> >     }
> >   }, [connection, requiredScopes, authorizationParams, resume]);
> >
> >   if (isLoading) {
> >     return (
> >       <Card className="w-full">
> >         <CardContent className="flex items-center justify-center p-6">
> >           <div className="text-center">
> >             <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-2"></div>
> >             <p className="text-sm text-muted-foreground">
> >               Connecting to {connection.replace("-", " ")}...
> >             </p>
> >           </div>
> >         </CardContent>
> >       </Card>
> >     );
> >   }
> >
> >   return (
> >     <Card className="w-full border-yellow-200 bg-yellow-50">
> >       <CardHeader>
> >         <CardTitle className="text-lg text-yellow-800">
> >           Authorization Required
> >         </CardTitle>
> >       </CardHeader>
> >       <CardContent className="space-y-4">
> >         <p className="text-sm text-yellow-700">
> >           To access your {connection.replace("-", " ")} data, you need to
> >           connect your account and authorize this application.
> >         </p>
> >         <p className="text-xs text-yellow-600">
> >           Required permissions:{" "}
> >           {requiredScopes
> >             .filter((scope: string) => scope && scope.trim() !== "")
> >             .join(", ")}
> >         </p>
> >         <Button onClick={startConnectAccountFlow} className="w-full">
> >           Connect &amp; Authorize {connection.replace("-", " ")}
> >         </Button>
> >       </CardContent>
> >     </Card>
> >   );
> > }
> > ```
> >
> > #### Create tools with integrated Token Vault support for retrieving third-party access tokens
> >
> > Next, create a tool that accesses Token Vault to fetch a Google access token to list all the Google Calendars a user has access to.
> >
> > Create `server/src/lib/tools/listUserCalendars.ts`:
> >
> > ```ts server/src/lib/tools/listUserCalendars.ts wrap lines theme={null}
> > import { tool } from "ai";
> > import { google } from "googleapis";
> > import { z } from "zod";
> >
> > import { getAccessTokenFromTokenVault } from "@auth0/ai-vercel";
> >
> > import type { ToolWrapper } from "@auth0/ai-vercel";
> >
> > /**
> >  * Tool: listUserCalendars
> >  * Lists all calendars the user has access to.
> >  * Uses the enhanced @auth0/ai SDK for token exchange with Token Vault.
> >  */
> > export const createListUserCalendarsTool = (
> >   googleCalendarWrapper: ToolWrapper
> > ) =>
> >   googleCalendarWrapper(
> >     tool({
> >       description: "List all calendars the user has access to",
> >       inputSchema: z.object({}),
> >       execute: async () => {
> >         // Get the access token from Token Vault using the enhanced SDK
> >         const token = getAccessTokenFromTokenVault();
> >
> >         const calendar = google.calendar("v3");
> >         const auth = new google.auth.OAuth2();
> >         auth.setCredentials({ access_token: token });
> >
> >         const res = await calendar.calendarList.list({ auth });
> >
> >         const calendars =
> >           res.data.items?.map((cal) => ({
> >             id: cal.id,
> >             name: cal.summary,
> >             accessRole: cal.accessRole,
> >           })) ?? [];
> >
> >         return calendars;
> >       },
> >     })
> >   );
> > ```
> >
> > #### Configure the API server with Google connection wrapper for calendar tools
> >
> > Instantiate an instance of `Auth0AI` with a pre-configured resource client. Then, create a wrapper for the Google Calendar Tool that sets up the token exchange. This allows you to directly exchange an Auth0 access token for a Google access token from Token Vault with the necessary Calendar scopes.
> >
> > Create `server/src/lib/auth0.ts`:
> >
> > ```ts server/src/lib/auth0.ts wrap lines theme={null}
> > import { SUBJECT_TOKEN_TYPES } from "@auth0/ai";
> > import { Auth0AI } from "@auth0/ai-vercel";
> >
> > import type { Context } from "hono";
> >
> > import type { ToolWrapper } from "@auth0/ai-vercel";
> > // Create an Auth0AI instance configured with reserver client credentials
> > const auth0AI = new Auth0AI({
> >   auth0: {
> >     domain: process.env.AUTH0_DOMAIN!,
> >     clientId: process.env.AUTH0_CUSTOM_API_CLIENT_ID!, // Resource server client ID for token exchange
> >     clientSecret: process.env.AUTH0_CUSTOM_API_CLIENT_SECRET!, // Resource server client secret
> >   },
> > });
> >
> > // Enhanced token exchange with Token Vault, setup with access token support
> > // This demonstrates the new API pattern where access tokens can be used directly
> > export const createGoogleCalendarTool = (c: Context): ToolWrapper => {
> >   const accessToken = c.get("auth")?.token;
> >   if (!accessToken) {
> >     throw new Error("Access token not available in auth context");
> >   }
> >   return auth0AI.withTokenVault({
> >     accessToken: async () => accessToken,
> >     subjectTokenType: SUBJECT_TOKEN_TYPES.SUBJECT_TYPE_ACCESS_TOKEN,
> >     connection: process.env.GOOGLE_CONNECTION_NAME || "google-oauth2",
> >     scopes: [
> >       "openid",
> >       "https://www.googleapis.com/auth/calendar.calendarlist.readonly", // Read-only access to calendar list
> >       "https://www.googleapis.com/auth/calendar.events.readonly", // Read-only access to events
> >     ],
> >   });
> > };
> > ```
> >
> > ### Create Hono API Chat API server with interrupt handling
> >
> > Create an AI-powered chat server using Hono, the Auth0 AI SDK, and the Vercel AI SDK that allows a user to chat with an AI assistant that can access their Google Calendar.
> >
> > The `withInterruptions()` function in the Auth0 AI SDK wraps the Vercel AI SDK `streamText()` function, enabling the Hono server to handle interrupts, which are special responses from Token Vault. An interrupt is sent if a tool call requires a new or updated access token, for example, if a user needs to re-authenticate or a new permission is needed.
> >
> > Create `server/src/index.ts`:
> >
> > ```ts server/src/index.ts wrap lines theme={null}
> > import {
> >   convertToModelMessages,
> >   createUIMessageStream,
> >   createUIMessageStreamResponse,
> >   generateId,
> >   streamText,
> > } from "ai";
> > import { Hono } from "hono";
> > import { cors } from "hono/cors";
> > import { decodeJwt } from "jose";
> >
> > import { openai } from "@ai-sdk/openai";
> > import { setAIContext } from "@auth0/ai-vercel";
> > import { errorSerializer, withInterruptions } from "@auth0/ai-vercel/interrupts";
> > import { serve } from "@hono/node-server";
> >
> > import { createGoogleCalendarTool } from "./lib/auth";
> > import { createListUserCalendarsTool } from "./lib/tools/listUserCalendars";
> > import { jwtAuthMiddleware } from "./middleware/auth";
> >
> > import type { ApiResponse } from "shared/dist";
> >
> > export const app = new Hono().post("/chat", jwtAuthMiddleware(), async (c) => {
> >     // auth middleware adds the auth context to the request
> >     const auth = c.get("auth");
> >
> >     const { messages: requestMessages } = await c.req.json();
> >
> >     // Generate a thread ID for this conversation
> >     const threadID = generateId();
> >
> >     // Set AI context for the tools to access
> >     setAIContext({ threadID });
> >
> >     // Create the Google Calendar wrapper with auth context
> >     const googleCalendarWrapper = createGoogleCalendarTool(c);
> >
> >     // Create tools with the auth context
> >     const listUserCalendars = createListUserCalendarsTool(googleCalendarWrapper);
> >
> >     // Use the messages from the request directly
> >     const tools = { listUserCalendars };
> >
> >     // note: you can see more examples of Hono API consumption with AI SDK here:
> >     // https://ai-sdk.dev/cookbook/api-servers/hono?utm_source=chatgpt.com#hono
> >
> >     const modelMessages = await convertToModelMessages(requestMessages);
> >     const date = new Date().toISOString();
> >
> >     const stream = createUIMessageStream({
> >       originalMessages: requestMessages,
> >       execute: withInterruptions(
> >         async ({ writer }) => {
> >           const result = streamText({
> >             model: openai("gpt-4o-mini"),
> >             system:
> >               `You are a helpful calendar assistant! You can help users with their calendar events and schedules. Keep your responses concise and helpful. Always format your responses as plain text. Do not use markdown formatting like **bold**, ##headers, or -bullet points. Use simple text formatting with line breaks and indentation only. The current date and time is ${date}.`,
> >             messages: modelMessages,
> >             tools,
> >
> >             onFinish: (output) => {
> >               if (output.finishReason === "tool-calls") {
> >                 const lastMessage = output.content[output.content.length - 1];
> >                 if (lastMessage?.type === "tool-error") {
> >                   const { toolName, toolCallId, error, input } = lastMessage;
> >                   const serializableError = {
> >                     cause: error,
> >                     toolCallId: toolCallId,
> >                     toolName: toolName,
> >                     toolArgs: input,
> >                   };
> >
> >                   throw serializableError;
> >                 }
> >               }
> >             },
> >           });
> >           writer.merge(
> >             result.toUIMessageStream({
> >               sendReasoning: true,
> >             })
> >           );
> >         },
> >         {
> >           messages: requestMessages,
> >           tools,
> >         }
> >       ),
> >       onError: errorSerializer((err) => {
> >         console.error("react-hono-ai-sdk route: stream error", err);
> >         return "Oops, an error occurred!";
> >       }),
> >     });
> >
> >     return createUIMessageStreamResponse({ stream });
> >   });
> >
> > // Start the server for Node.js
> > const port = Number(process.env.PORT) || 3000;
> >
> > console.log(`🚀 Server starting on port ${port}`);
> > serve({ fetch: app.fetch, port });
> >
> > console.log(`✅ Server running on http://localhost:${port}`);
> > ```
> >
> > ### Implement interrupt handling in React
> >
> > Update your chat component to handle step-up auth interrupts:
> >
> > ```tsx client/src/components/Chat.tsx wrap lines highlight={9,24,100-104,106-109} theme={null}
> > import {
> >   DefaultChatTransport,
> >   lastAssistantMessageIsCompleteWithToolCalls,
> > } from "ai";
> > import { Loader2, Send, Trash2 } from "lucide-react";
> > import { useState } from "react";
> >
> > import { useChat } from "@ai-sdk/react";
> > import { useInterruptions } from "@auth0/ai-vercel/react";
> > import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
> >
> > import { useAuth0 } from "../hooks/useAuth0";
> > import { TokenVaultConsentPopup } from "./TokenVaultConsentPopup";
> > import { MarkdownText } from "./MarkdownText";
> > import { Button } from "./ui/button";
> > import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
> > import { Input } from "./ui/input";
> >
> > import type { TextUIPart, UIMessage } from "ai";
> > const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
> >
> > export function Chat() {
> >   const { getToken } = useAuth0();
> >   const [input, setInput] = useState<string>("");
> >   const chatHelpers = useInterruptions((errorHandler) =>
> >     useChat({
> >       transport: new DefaultChatTransport({
> >         api: `${SERVER_URL}/chat`,
> >         fetch: (async (url: string | URL | Request, init?: RequestInit) => {
> >           const token = await getToken();
> >           return fetch(url, {
> >             ...init,
> >             headers: {
> >               "Content-Type": "application/json",
> >               ...init?.headers,
> >               Authorization: `Bearer ${token}`,
> >             },
> >           });
> >         }) as typeof fetch,
> >       }),
> >       onError: errorHandler((error) => {
> >         console.error("Chat error:", error);
> >       }),
> >       sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
> >     })
> >   );
> >
> >   const { messages, sendMessage, status, error, setMessages, toolInterrupt } =
> >     chatHelpers;
> >
> >   const clearMessages = () => {
> >     setMessages([]);
> >   };
> >
> >   return (
> >     <Card className="w-full max-w-2xl mx-auto">
> >       <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
> >         <CardTitle className="text-lg font-semibold">
> >           Calendar Assistant
> >         </CardTitle>
> >         {messages.length > 0 && (
> >           <Button
> >             variant="outline"
> >             size="sm"
> >             onClick={clearMessages}
> >             className="h-8 w-8 p-0"
> >           >
> >             <Trash2 className="h-4 w-4" />
> >           </Button>
> >         )}
> >       </CardHeader>
> >       <CardContent className="space-y-4">
> >         {/* Messages */}
> >         <div className="space-y-4 max-h-96 overflow-y-auto">
> >           {messages.length === 0 ? (
> >             <div className="text-center text-muted-foreground py-8">
> >               <p className="text-sm">Ask me about your calendar events!</p>
> >               <p className="text-xs mt-1">
> >                 Try: "What meetings do I have today?" or "Show me my upcoming
> >                 events"
> >               </p>
> >             </div>
> >           ) : (
> >             messages.map((message) => (
> >               <MessageBubble key={message.id} message={message} />
> >             ))
> >           )}
> >           {status === "streaming" && (
> >             <div className="flex justify-start">
> >               <div className="bg-muted rounded-lg px-3 py-2 max-w-[80%] flex items-center gap-2">
> >                 <Loader2 className="h-4 w-4 animate-spin" />
> >                 <span className="text-sm text-muted-foreground">
> >                   Thinking...
> >                 </span>
> >               </div>
> >             </div>
> >           )}
> >         </div>
> >
> >         {/* Error message - hide if it's an Auth0 interrupt (we show the popup instead) */}
> >         {error && !TokenVaultInterrupt.isInterrupt(toolInterrupt) && (
> >           <div className="bg-destructive/10 text-destructive text-sm p-3 rounded-lg">
> >             Error: {error.message}
> >           </div>
> >         )}
> >
> >         {/* Step-Up Auth Interrupt Handling */}
> >         {TokenVaultInterrupt.isInterrupt(toolInterrupt) && (
> >           <TokenVaultConsentPopup interrupt={toolInterrupt} />
> >         )}
> >
> >         {/* Input form */}
> >         <form
> >           onSubmit={(e) => {
> >             e.preventDefault();
> >             sendMessage({ text: input });
> >             setInput("");
> >           }}
> >           className="flex gap-2"
> >         >
> >           <Input
> >             value={input}
> >             onChange={(e) => setInput(e.target.value)}
> >             placeholder="Ask about your calendar..."
> >             disabled={status === "streaming"}
> >             className="flex-1"
> >           />
> >           <Button
> >             className="h-10"
> >             type="submit"
> >             disabled={status === "streaming" || !input.trim()}
> >           >
> >             <Send className="h-4 w-4" />
> >           </Button>
> >         </form>
> >       </CardContent>
> >     </Card>
> >   );
> > }
> >
> > function MessageBubble({ message }: { message: UIMessage }) {
> >   const isUser = message.role === "user";
> >
> >   // Get all text content from the message parts
> >   const textContent = message.parts
> >     .filter((part) => part.type === "text")
> >     .map((part) => (part as TextUIPart).text)
> >     .join("");
> >
> >   return (
> >     <div className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
> >       <div
> >         className={`rounded-lg px-3 py-2 max-w-[80%] ${
> >           isUser ? "bg-primary text-primary-foreground" : "bg-muted"
> >         }`}
> >       >
> >         {isUser ? (
> >           <p className="text-sm whitespace-pre-wrap">{textContent}</p>
> >         ) : (
> >           <div className="text-sm">
> >             <MarkdownText>{textContent}</MarkdownText>
> >           </div>
> >         )}
> >       </div>
> >     </div>
> >   );
> > }
> > ```
> >
> > ### Test your application
> >
> > 1. Start the client and server using Turbo: `npm run dev`.
> > 2. Navigate to `http://localhost:5173`.
> > 3. Log in with Google and ask your AI agent about your calendar.
> >
> > The application will automatically prompt for additional calendar permissions when needed using Auth0's step-up authorization flow.
> >
> > That's it! You've successfully integrated Token Vault with access tokens into your React SPA + Vercel AI application.
> >
> > Explore [the example app on GitHub](https://github.com/auth0/auth0-ai-js/tree/main/examples/calling-apis/spa-with-backend-api/react-hono-ai-sdk).

## 次のステップ

これで、ツール呼び出しに使うアクセストークンを取得する機能がアプリケーションに追加されました。次は以下をご覧ください。

* [ユーザーに代わってファーストパーティAPIを呼び出す](https://auth0.com/ai/docs/ja-jp/intro/call-first-party-apis-on-users-behalf.md)に関するドキュメント
* Auth0の[Token Vault](https://auth0.com/docs/ja-jp/secure/call-apis-on-users-behalf/token-vault.md)が、サポート対象のIDプロバイダーのトークンをどのように管理するかについて詳しく確認する
