- Initiate a Connected Accounts flow that will allow the user to connect their Google account and grant access to the AI agent.
- Retrieve access tokens for a Google social connection.
- Integrate with an AI agent to call Google APIs.
Pick your tech stack
LangGraph.js + Next.js
Vercel AI + Next.js
LangGraph + FastAPI
Vercel AI + React SPA
Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
Create an Auth0 Application
- Navigate to Applications > Applications in the left sidebar.
- Click the Create Application button in the top right.
- In the pop-up select Regular Web Applications and click Create.
- Once the Application is created, switch to the Settings tab.
- Scroll down to the Application URIs section.
- Set Allowed Callback URLs as:
http://localhost:3000/auth/callback - Set Allowed Logout URLs as:
http://localhost:3000 - Click Save in the bottom right to save your changes.
Configure My Account API
In the Auth0 Dashboard, configure the My Account API:
- Navigate to Authentication > APIs, locate the My Account API banner, and select Activate to activate the Auth0 My Account API.
- Once activated, select Auth0 My Account API and then select the Applications tab.
- Toggle your client application to authorize it to access the My Account API.
- In the dropdown menu, select the Connected Accounts scopes for the application, ensuring that at a minimum, the
create:me:connected_accountspermission is selected. - Select Update.
- Next, navigate to the Settings tab. Under Access Settings, select Allow Skipping User Consent.
Define a Multi-Resource Refresh Token policy for your Application
You can quickly define a refresh token policy for your application to use when requesting access tokens for the My Account API by doing the following:
- Navigate to Applications > Applications and select your client application.
- On the Settings tab, scroll down to the Multi-Resource Refresh Token section.
- Select Edit Configuration and then enable the MRRT toggle for the Auth0 My Account API.
Configure Google Social Integration
OpenAI Platform
- Use sample app (recommended)
- Integrate into your app
Download sample app
Start by downloading and extracting the sample app. Then open in your preferred IDE.Create your environment file
In the root directory of your project, create a new.env.local file and add the following content:# 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
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET, navigate to Applications > 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 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 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:openssl rand -hex 32
OPENAI_API_KEY="YOUR_API_KEY"
Install packages
Ensure you havenpm installed or follow the instructions to install npm in its documentation.
In the root directory of your project, run the following command to install the required packages:npm install
Test your application
Start the application withnpm run all:dev. Then, navigate to http://localhost:3000.Install packages
In the root directory of your project, install the following packages:@auth0/ai-langchain: Auth0 AI SDK for 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.
npm install @auth0/ai-langchain@4 @langchain/[email protected] @langchain/[email protected] @langchain/[email protected] @langchain/[email protected] [email protected] [email protected]
Update your environment file
Add to your environment file the following variables:# 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
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET, navigate to Applications > 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 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 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:openssl rand -hex 32
OPENAI_API_KEY="YOUR_API_KEY"
Set up Token Vault for Google social connection
Use the Auth0 AI SDK for LangChain to get an access token for the Google Social Connection using Token Vault:connection: pass in the name of the connection you want to access.scopes: pass in the scopes to be authorized for this connection.
src/lib/auth0-ai.ts to instantiate the Auth0 AI SDK client: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: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;
};
/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.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
langgraph.json, add the path to your auth file:{
"node_version": "20",
"graphs": {
"agent": "./src/lib/agent.ts:agent"
},
"env": ".env",
"auth": {
"path": "./src/lib/auth.ts:authHandler"
}
}
auth.ts file, add your auth logic: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 usesGmailSearch from the @langchain/community tools. This tool will use the access token provided by Token Vault to query for emails.//...
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(),
},
})
);
//...
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:npx @auth0/ai-components add TokenVault
src/components/TokenVaultInterruptHandler.tsx, with the following code: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>
);
}
TokenVaultInterruptHandler component, for example://...
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>
);
}
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.Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
Create an Auth0 Application
- Navigate to Applications > Applications in the left sidebar.
- Click the Create Application button in the top right.
- In the pop-up select Regular Web Applications and click Create.
- Once the Application is created, switch to the Settings tab.
- Scroll down to the Application URIs section.
- Set Allowed Callback URLs as:
http://localhost:3000/auth/callback - Set Allowed Logout URLs as:
http://localhost:3000 - Click Save in the bottom right to save your changes.
Configure My Account API
In the Auth0 Dashboard, configure the My Account API:
- Navigate to Authentication > APIs, locate the My Account API banner, and select Activate to activate the Auth0 My Account API.
- Once activated, select Auth0 My Account API and then select the Applications tab.
- Toggle your client application to authorize it to access the My Account API.
- In the dropdown menu, select the Connected Accounts scopes for the application, ensuring that at a minimum, the
create:me:connected_accountspermission is selected. - Select Update.
- Next, navigate to the Settings tab. Under Access Settings, select Allow Skipping User Consent.
Define a Multi-Resource Refresh Token policy for your Application
You can quickly define a refresh token policy for your application to use when requesting access tokens for the My Account API by doing the following:
- Navigate to Applications > Applications and select your client application.
- On the Settings tab, scroll down to the Multi-Resource Refresh Token section.
- Select Edit Configuration and then enable the MRRT toggle for the Auth0 My Account API.
Configure Google Social Integration
OpenAI Platform
- Use sample app (recommended)
- Integrate into your app
Download sample app
Start by downloading and extracting the sample app. Then open in your preferred IDE.Create your environment file
In the root directory of your project, create an.env.local file and add the following content:# 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"
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET, navigate to Applications > 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:openssl rand -hex 32
OPENAI_API_KEY="YOUR_API_KEY"
Install packages
Ensure you havenpm installed or follow the instructions here to install npm.
In the root directory of your project, run the following command to install the required packages:npm install
Test your application
Start the application withnpm 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.Install packages
In the root directory of your project, install the following packages:@auth0/ai-vercel: Auth0 AI SDK for Vercel AI built for AI agents powered by the Vercel AI SDK.ai: Core Vercel AI SDK module that interacts with various AI model providers.@ai-sdk/openai: OpenAI provider for the Vercel AI SDK.@ai-sdk/react: React 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.
npm install @auth0/ai-vercel@4 [email protected] @ai-sdk/[email protected] @ai-sdk/[email protected] [email protected] googleapis@161
Update your environment file
Add the following variables to your environment file:# 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"
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET, navigate to Applications > 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:openssl rand -hex 32
Set up Token Vault for Google social connection
Use the Auth0 AI SDK for Vercel AI to get an access token for the Google Social Connection using Token Vault: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.
src/lib/auth0-ai.ts to instantiate the Auth0 AI SDK client: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: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: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:npx @auth0/ai-components add TokenVault
src/components/TokenVaultInterruptHandler.tsx with the following code: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>
);
}
TokenVaultInterruptHandler component, for example://...
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>
);
}
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://...
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 = 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.Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
Create an Auth0 Application
- Navigate to Applications > Applications in the left sidebar.
- Click the Create Application button in the top right.
- In the pop-up select Regular Web Applications and click Create.
- Once the Application is created, switch to the Settings tab.
- Scroll down to the Application URIs section.
- Set Allowed Callback URLs as:
http://localhost:3000/auth/callback - Set Allowed Logout URLs as:
http://localhost:3000 - Click Save in the bottom right to save your changes.
Configure My Account API
In the Auth0 Dashboard, configure the My Account API:
- Navigate to Authentication > APIs, locate the My Account API banner, and select Activate to activate the Auth0 My Account API.
- Once activated, select Auth0 My Account API and then select the Applications tab.
- Toggle your client application to authorize it to access the My Account API.
- In the dropdown menu, select the Connected Accounts scopes for the application, ensuring that at a minimum, the
create:me:connected_accountspermission is selected. - Select Update.
- Next, navigate to the Settings tab. Under Access Settings, select Allow Skipping User Consent.
Define a Multi-Resource Refresh Token policy for your Application
You can quickly define a refresh token policy for your application to use when requesting access tokens for the My Account API by doing the following:
- Navigate to Applications > Applications and select your client application.
- On the Settings tab, scroll down to the Multi-Resource Refresh Token section.
- Select Edit Configuration and then enable the MRRT toggle for the Auth0 My Account API.
Configure Google Social Integration
OpenAI Platform
- Use sample app (recommended)
- Integrate into your app
Download sample app
Start by downloading and extracting the sample app. Then open in your preferred IDE.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 thebackend 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 in its documentation.cd backend
uv sync
Create your environment file
In thebackend directory of your project, create a new file and name it .env and add the following content:# 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
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET, navigate to Applications > 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:openssl rand -hex 32
OPENAI_API_KEY="YOUR_API_KEY"
frontend directory of your project, copy the .env.example file to .env. No further customization required.Install frontend packages
Ensure you havenpm installed or follow the instructions to install npm in its documentation.
Navigate to the frontend directory of your project and install the required packages:cd frontend
npm install
Run your application
To run your application, start the FastAPI backend, LangGraph server, and the frontend in three terminals:- In one terminal, start the FastAPI backend:
cd backend
source .venv/bin/activate
uv pip install auth0_fastapi
fastapi dev app/main.py
- In a second new terminal, start the LangGraph server:
cd backend
source .venv/bin/activate
uv pip install -U langgraph-api
langgraph dev --port 54367 --allow-blocking
- In a third new terminal, start the frontend:
cd frontend
npm run dev
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.Install packages
In thebackend directory of your project, install the following dependencies:auth0-ai-langchain: Auth0 AI SDK for LangChain built for AI agents powered by LangChain.langgraph: 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.
cd backend
uv sync
uv add "auth0-ai-langchain>=1.0.0b5" "langgraph>=0.5.4" langchain-openai "langgraph-cli[inmem]>=0.3.6" google-api-python-client --prerelease=allow
Update your environment file
In thebackend directory of your project, add the following variables to your environment file:# 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
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET, navigate to Applications > 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.openssl rand -hex 32
Set up Token Vault for Google social connection
Use the Auth0 AI SDK for LangChain to get an access token for the Google Social Connection using Token Vault:connection: pass in the name of the connection you want to access.scopes: pass in the scopes to be authorized for this connection.
app/core/auth0_ai.py to instantiate the Auth0 AI SDK client: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": "[email protected]", "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, inapp/api/routes/chat.py:# ...
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: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:# ...
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:cd frontend
npm install @auth0/ai @langchain/langgraph-sdk
npx @auth0/ai-components add TokenVault
src/components/TokenVaultInterruptHandler.tsx, with the following code: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>
);
}
TokenVaultInterruptHandler component, for example://...
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>
);
}
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.Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
Create an Auth0 Application
- Navigate to Applications > Applications in the left sidebar.
- Click the Create Application button in the top right.
- In the pop-up select Regular Web Applications and click Create.
- Once the Application is created, switch to the Settings tab.
- Scroll down to the Application URIs section.
- Set Allowed Callback URLs as:
http://localhost:3000/auth/callback - Set Allowed Logout URLs as:
http://localhost:3000 - Click Save in the bottom right to save your changes.
Configure My Account API
In the Auth0 Dashboard, configure the My Account API:
- Navigate to Authentication > APIs, locate the My Account API banner, and select Activate to activate the Auth0 My Account API.
- Once activated, select Auth0 My Account API and then select the Applications tab.
- Toggle your client application to authorize it to access the My Account API.
- In the dropdown menu, select the Connected Accounts scopes for the application, ensuring that at a minimum, the
create:me:connected_accountspermission is selected. - Select Update.
- Next, navigate to the Settings tab. Under Access Settings, select Allow Skipping User Consent.
Define a Multi-Resource Refresh Token policy for your Application
You can quickly define a refresh token policy for your application to use when requesting access tokens for the My Account API by doing the following:
- Navigate to Applications > Applications and select your client application.
- On the Settings tab, scroll down to the Multi-Resource Refresh Token section.
- Select Edit Configuration and then enable the MRRT toggle for the Auth0 My Account API.
Configure Google Social Integration
OpenAI Platform
Key differences from Next.js approach
This React SPA implementation differs from the Next.js example in a few important ways:- 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.
- Client-Side Authorization: Client login and step-up authorization are handled client-side using
@auth0/auth0-spa-js. - Resource Server Client: Requires a special Resource Server Client configured for token exchange with Token Vault.
- Interrupt Handling: The React client manages tool access errors and step-up authorization using interrupts that trigger a redirect for re-authorization.
- Use sample app (recommended)
- Integrate into your app
Download sample app
Start by downloading and extracting the sample app. Then open in your preferred IDE.Create your environment files
Add separate.env files with environment variables for the client and server.Client (client/.env)
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
VITE_AUTH0_DOMAIN, and VITE_AUTH0_CLIENT_ID, navigate to Applications > 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 APIs > 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)
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
AUTH0_DOMAIN, AUTH0_AUDIENCE, and AUTH0_CUSTOM_API_CLIENT_ID, and AUTH0_CUSTOM_API_CLIENT_SECRET navigate to Applications > 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:OPENAI_API_KEY="YOUR_API_KEY"
Install packages
Ensure you havenpm installed or follow the instructions to install npm in its documentation.
In the root directory of your project, run the following command to install the required packages:npm install
Test your application
- Start the client and server using Turbo:
npm run dev. - Navigate to
http://localhost:5173. - Log in with Google and ask your AI agent about your calendar.
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 built for AI agentsai: Core Vercel AI SDK module
@hono/node-server: Node.js server adapter for Honohono: Lightweight web frameworkai: Core Vercel AI SDK module@ai-sdk/openai: OpenAI providergoogleapis: Node.js client for Google APIsjose: 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:
# 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)
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
VITE_AUTH0_DOMAIN, and VITE_AUTH0_CLIENT_ID, navigate to Applications > 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 APIs > 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)
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
AUTH0_DOMAIN, AUTH0_AUDIENCE, and AUTH0_CUSTOM_API_CLIENT_ID, and AUTH0_CUSTOM_API_CLIENT_SECRET navigate to Applications > 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:OPENAI_API_KEY="YOUR_API_KEY"
Create an Auth0 Provider and initialize the Auth0 SPA SDK
Createclient/src/lib/auth0.ts and initialize the Auth0 SPA SDK with your Auth0 application details, along with configuration for token storage and refresh tokens: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;
};
client/src/components/Auth0Provider.tsx: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>
);
};
Auth0Provider in client/src/main.tsx: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, which uses refresh tokens, this React SPA approach uses access tokens for token exchange with Token Vault. The SPA handles step-up authorization using theloginWithPopup() 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: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 & 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.Createserver/src/lib/tools/listUserCalendars.ts: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 ofAuth0AI 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: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.ThewithInterruptions() 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: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 = 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: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
- Start the client and server using Turbo:
npm run dev. - Navigate to
http://localhost:5173. - Log in with Google and ask your AI agent about your calendar.
Next steps
You have successfully added the ability to get access tokens for tool calling to your application. For next steps:- Call your APIs on user’s behalf docs.
- Learn more about how Auth0’s Token Vault manages the tokens of supported identity providers.