- 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
To continue with this quickstart, you need to have an Auth0 account.
Create an Auth0 Application
Go to your Auth0 Dashboard to create a new 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.
Enable Token Exchange Grant
Enable the Token Exchange Grant for your Auth0 Application. Go to Applications > [Your Application] > Settings > Advanced > Grant Types and enable the Token Exchange grant type.
Configure Google Social Integration
Set up a Google developer account that allows for third-party API calls by following the Google Social Integration instructions.
OpenAI Platform
Set up an OpenAI account and API key.
Prepare Next.js app
Recommended: To use a starter template, clone the Auth0 AI samples repository:Report incorrect code
Copy
Ask AI
git clone https://github.com/auth0-samples/auth0-ai-samples.git
cd auth0-ai-samples/authenticate-users/langchain-next-js
Install dependencies
In the root directory of your project, install the following dependencies:@auth0/ai-langchain: Auth0 AI SDK for LangChain built for GenAI applications 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.
Report incorrect code
Copy
Ask AI
npm install @auth0/ai-langchain@3 @langchain/community@0.3 @langchain/core@0.3 @langchain/langgraph@0.3 @langchain/openai@0.6 langchain@0.3 langgraph-nextjs-api-passthrough@0.1
Update the environment file
Copy the.env.example file to .env.local and update the variables with your Auth0 credentials. You can find your Auth0 domain, client ID, and client secret in the application you created in the Auth0 Dashboard.Get access tokens for others APIs
Use the Auth0 AI SDK for LangChain to get access tokens for third-party APIs.Set up Token Vault for Google social connection
Set up the Auth0 AI SDK for a Google Social Connection. This allows you to get access tokens for a Google social connection using Token Vault:connection: pass in the name of the connection you want the user to sign up for/log into.scopes: pass in the scopes for the service you want to get access to.
src/lib/auth0-ai.ts and instantiate a new Auth0 AI SDK client:src/lib/auth0-ai.ts
Report incorrect code
Copy
Ask AI
import { Auth0AI, getAccessTokenForConnection } from "@auth0/ai-langchain";
// Get the access token for a connection via Auth0
export const getAccessToken = async () => getAccessTokenForConnection();
const auth0AI = new Auth0AI();
// Connection for Google services
export const withGoogleConnection = auth0AI.withTokenForConnection({
connection: "google-oauth2",
scopes: ["https://www.googleapis.com/auth/gmail.readonly"],
});
Pass credentials to the tools
Update the/src/lib/auth0.ts file with the following code:src/lib/auth0.ts
Report incorrect code
Copy
Ask AI
//...
//... existing code
// Get the refresh token from Auth0 session
export const getRefreshToken = async () => {
const session = await auth0.getSession();
return session?.tokenSet?.refreshToken;
};
/src/app/api/chat/[..._path]/route.ts file with the following code. The refreshToken will be passed to your LangGraph agent so we can use it from the Auth0 AI SDK to get Google access tokens from the server.src/app/api/chat/[..._path]/route.ts
Report incorrect code
Copy
Ask AI
import { initApiPassthrough } from "langgraph-nextjs-api-passthrough";
import { getRefreshToken } from "@/lib/auth0";
export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } =
initApiPassthrough({
apiUrl: process.env.LANGGRAPH_API_URL,
baseRoute: "chat/",
bodyParameters: async (req, body) => {
if (
req.nextUrl.pathname.endsWith("/runs/stream") &&
req.method === "POST"
) {
return {
...body,
config: {
configurable: {
_credentials: {
refreshToken: await getRefreshToken(),
},
},
},
};
}
return body;
},
});
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 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 will use theGmailSearch from the @langchain/community tools. This tool will use the access token provided by Token Vault to query for emails.src/lib/agent.ts
Report incorrect code
Copy
Ask AI
//...
import { GmailSearch } from "@langchain/community/tools/gmail";
import { getAccessToken, withGoogleConnection } from "./auth0-ai";
//... existing code
// Provide the access token to the Gmail tools
const gmailParams = {
credentials: {
accessToken: getAccessToken,
},
};
const tools = [
//... existing tools
withGoogleConnection(new GmailSearch(gmailParams)),
];
//... 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,
});
.env.local
Report incorrect code
Copy
Ask AI
# ...
# You can use any provider of your choice supported by Vercel AI
OPENAI_API_KEY="YOUR_API_KEY"
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.Let us implement step-up authorization.Install the Auth0 AI Components for Next.js to get the required UI components:Report incorrect code
Copy
Ask AI
npx @auth0/ai-components add FederatedConnections
src/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler.tsx, with the following code:src/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler.tsx
Report incorrect code
Copy
Ask AI
import { FederatedConnectionInterrupt } from "@auth0/ai/interrupts";
import type { Interrupt } from "@langchain/langgraph-sdk";
import { EnsureAPIAccess } from "@/components/auth0-ai/FederatedConnections";
interface FederatedConnectionInterruptHandlerProps {
interrupt: Interrupt | undefined | null;
onFinish: () => void;
}
export function FederatedConnectionInterruptHandler({
interrupt,
onFinish,
}: FederatedConnectionInterruptHandlerProps) {
if (
!interrupt ||
!FederatedConnectionInterrupt.isInterrupt(interrupt.value)
) {
return null;
}
return (
<div key={interrupt.ns?.join("")} className="whitespace-pre-wrap">
<EnsureAPIAccess
mode="popup"
interrupt={interrupt.value}
onFinish={onFinish}
connectWidget={{
title: "Authorization Required.",
description: interrupt.value.message,
action: { label: "Authorize" },
}}
/>
</div>
);
}
src/components/chat-window.tsx file to include the FederatedConnectionInterruptHandler component:src/components/chat-window.tsx
Report incorrect code
Copy
Ask AI
//...
import { FederatedConnectionInterruptHandler } from '@/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler';
//... 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">
<FederatedConnectionInterruptHandler interrupt={chat.interrupt} onFinish={() => chat.submit(null)} />
</div>
</>
)
}
{/* ... existing code */}
></StickyToBottomContent>
</StickToBottom>
);
}
Test your application
Start the application withnpm run all:dev. Then, navigate to http://localhost:3000.This will open the LangGraph Studio in a new tab. You can close it as we won’t
require it for testing the application.
Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
To continue with this quickstart, you need to have an Auth0 account.
Create an Auth0 Application
Go to your Auth0 Dashboard to create a new 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.
Enable Token Exchange Grant
Enable the Token Exchange Grant for your Auth0 Application. Go to Applications > [Your Application] > Settings > Advanced > Grant Types and enable the Token Exchange grant type.
Configure Google Social Integration
Set up a Google developer account that allows for third-party API calls by following the Google Social Integration instructions.
OpenAI Platform
Set up an OpenAI account and API key.
Prerequisites
Before getting started, make sure you have completed the following steps:- Create an Auth0 Account and a Dev Tenant
- Create and configure a Regular Web Application to use with this quickstart.
- Complete the User Authentication quickstart or download a starter template.
- Configure Google Social Connection (see Google Sign-in and Authorization) and select the Calender scope.
- Set up an OpenAI account and API key
Prepare Next.js app
Recommended: To use a starter template, clone the Auth0 AI samples repository:Report incorrect code
Copy
Ask AI
git clone https://github.com/auth0-samples/auth0-ai-samples.git
cd auth0-ai-samples/authenticate-users/vercel-ai-next-js
Install dependencies
In the root directory of your project, install the following dependencies:@auth0/ai-vercel: Auth0 AI SDK for Vercel AI built for GenAI applications 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.
Report incorrect code
Copy
Ask AI
npm install @auth0/ai-vercel@3 ai@4 @ai-sdk/openai@1 @ai-sdk/react@1 zod@3 googleapis@148
Update the environment file
Copy the.env.example file to .env.local and update the variables with your Auth0 credentials. You can find your Auth0 domain, client ID, and client secret in the application you created in the Auth0 Dashboard.Get access tokens for other APIs
Use the Auth0 AI SDK to get access tokens for third-party APIs.Set up Token Vault for Google Social Connection
Setup Auth0 AI SDK for Google Social Connection. This will allow you to get access tokens for Google Social Connection using Token Vault.connection: pass in the name of the connection you want the user to sign up for/log into.refreshToken: pass in the function to get the refresh token from the current session.scopes: pass in the scopes for the service you want to get access to.
src/lib/auth0-ai.ts and instantiate a new Auth0 AI SDK client:src/lib/auth0-ai.ts
Report incorrect code
Copy
Ask AI
import { Auth0AI, getAccessTokenForConnection } from "@auth0/ai-vercel";
import { getRefreshToken } from "./auth0";
// Get the access token for a connection via Auth0
export const getAccessToken = async () => getAccessTokenForConnection();
const auth0AI = new Auth0AI();
// Connection for Google services
export const withGoogleConnection = auth0AI.withTokenForConnection({
connection: "google-oauth2",
scopes: ["https://www.googleapis.com/auth/calendar.events"],
refreshToken: getRefreshToken,
});
/src/lib/auth0.ts file with the following code:src/lib/auth0.ts
Report incorrect code
Copy
Ask AI
//...
//... existing code
// 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 our example, we create a file atsrc/lib/tools/google-calendar.ts. In the file, we define a tool, getCalendarEventsTool, that uses the access token with the Google Calendar API to query for calendar events in a specified date:src/lib/tools/google-calendar.ts
Report incorrect code
Copy
Ask AI
import { tool } from 'ai';
import { endOfDay, formatISO, startOfDay } from 'date-fns';
import { GaxiosError } from 'gaxios';
import { google } from 'googleapis';
import { z } from 'zod';
import { FederatedConnectionError } 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`,
parameters: 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 FederatedConnectionError(`Authorization required to access the Federated Connection`);
}
}
throw error;
}
},
}),
);
.env.local
Report incorrect code
Copy
Ask AI
# ...
# You can use any provider of your choice supported by Vercel AI
OPENAI_API_KEY="YOUR_API_KEY"
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.Let us implement step-up authorization.Install Auth0 AI Components for Next.js to get the required UI components.Report incorrect code
Copy
Ask AI
npx @auth0/ai-components add FederatedConnections
src/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler.tsx with the following code:src/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler.tsx
Report incorrect code
Copy
Ask AI
import { FederatedConnectionInterrupt } from "@auth0/ai/interrupts";
import type { Auth0InterruptionUI } from "@auth0/ai-vercel/react";
import { EnsureAPIAccess } from "@/components/auth0-ai/FederatedConnections";
interface FederatedConnectionInterruptHandlerProps {
interrupt: Auth0InterruptionUI | null;
}
export function FederatedConnectionInterruptHandler({
interrupt,
}: FederatedConnectionInterruptHandlerProps) {
if (!FederatedConnectionInterrupt.isInterrupt(interrupt)) {
return null;
}
return (
<div key={interrupt.name} className="whitespace-pre-wrap">
<EnsureAPIAccess
mode="popup"
interrupt={interrupt}
connectWidget={{
title: "Authorization Required.",
description: interrupt.message,
action: { label: "Authorize" },
}}
/>
</div>
);
}
src/components/chat-window.tsx file to include the FederatedConnectionInterruptHandler component:src/components/chat-window.tsx
Report incorrect code
Copy
Ask AI
//...
import { useInterruptions } from '@auth0/ai-vercel/react';
import { FederatedConnectionInterruptHandler } from '@/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler';
//... 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">
<FederatedConnectionInterruptHandler interrupt={chat.toolInterrupt} />
</div>
</>
)
}
{/* ... existing code */}
></StickyToBottomContent>
</StickToBottom>
);
}
Add the tool to the AI agent
Handle the interrupts from the Agent and call the tool from your AI app to get calendar events. Update the/src/app/api/chat/route.ts file with the following code:src/app/api/chat/route.ts
Report incorrect code
Copy
Ask AI
//...
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,
};
return createDataStreamResponse({
execute: withInterruptions(
async (dataStream: DataStreamWriter) => {
const result = streamText({
model: openai("gpt-4o-mini"),
system: AGENT_SYSTEM_TEMPLATE,
messages,
maxSteps: 5,
tools,
});
result.mergeIntoDataStream(dataStream, {
sendReasoning: true,
});
},
{
messages,
tools,
}
),
onError: errorSerializer((err: any) => {
console.log(err);
return `An error occurred! ${err.message}`;
}),
});
}
Test your application
Start the application withnpm run dev. Then, navigate to http://localhost:3000. If you have already logged in, make sure to log out and log back in using Google. Then, ask your AI Agent a question about your calendar!That’s it! You successfully integrated external tool-calling into your project.Explore the example app on GitHub.Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
To continue with this quickstart, you need to have an Auth0 account.
Create an Auth0 Application
Go to your Auth0 Dashboard to create a new 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:8000/api/auth/callback - Set Allowed Logout URLs as:
http://localhost:5173 - Click Save in the bottom right to save your changes.
Enable Token Exchange Grant
Enable the Token Exchange Grant for your Auth0 Application. Go to Applications > [Your Application] > Settings > Advanced > Grant Types and enable the Token Exchange grant type.
Configure Google Social Integration
Set up a Google developer account that allows for third-party API calls by following the Google Social Integration instructions.
OpenAI Platform
Set up an OpenAI account and API key.
Prepare the FastAPI app
Recommended: Use the starter template by cloning the Auth0 AI samples repository:Report incorrect code
Copy
Ask AI
git clone https://github.com/auth0-samples/auth0-ai-samples.git
cd auth0-ai-samples/authenticate-users/langchain-fastapi-py
backend/: contains the backend code for the Web app and 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 dependencies
In thebackend directory of your project, install the following dependencies:auth0-ai-langchain: Auth0 AI SDK for LangChain built for GenAI applications 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.
Report incorrect code
Copy
Ask AI
cd backend
uv sync
uv add "auth0-ai-langchain>=1.0.0b3" "langgraph>=0.5.4" langchain-openai "langgraph-cli[inmem]" google-api-python-client --prerelease=allow
Update the environment file
Copy the.env.example file to .env and update the variables with your Auth0 credentials. You can find your Auth0 domain, client ID, and client secret in the application you created in the Auth0 Dashboard.Get access tokens for other’s APIs
Use the Auth0 AI SDK for LangChain to get access tokens for third-party APIs.Set up Token Vault for Google social connection
Set up the Auth0 AI SDK for a Google social connection. This allows you to get access tokens for a Google social connection using Token Vault:connection: pass in the name of the connection you want the user to sign up for/log into.scopes: pass in the scopes for the service you want to get access to.
app/core/auth0_ai.py and instantiate a new Auth0 AI SDK client:app/core/auth0_ai.py
Report incorrect code
Copy
Ask AI
from auth0_ai.authorizers.types import Auth0ClientParams
from auth0_ai_langchain.auth0_ai import Auth0AI
from langchain_core.runnables import ensure_config
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_federated_connection(
connection="google-oauth2",
scopes=["https://www.googleapis.com/auth/calendar.events"],
)
Pass credentials to the agent
Update the API route to pass the user session data including federated tokens to the agent inapp/api/routes/chat.py:app/api/routes/chat.py
Report incorrect code
Copy
Ask AI
# ...
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 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 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 step, you’ll create a LangChain tool that uses a federated connection to access third-party APIs.Create a Google Calendar tool inapp/agents/tools/google_calendar.py:app/agents/tools/google_calendar.py
Report incorrect code
Copy
Ask AI
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.federated_connections import (
get_access_token_for_connection,
)
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_for_connection()
if not google_access_token:
raise ValueError(
"Authorization required to access the Federated Connection 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
The AI agent processes and runs the user’s request through the AI pipeline, including the tool call. Update theapp/agents/assistant0.py file to add the tool to the agent.app/agents/assistant0.py
Report incorrect code
Copy
Ask AI
# ...
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(),
)
.env
Report incorrect code
Copy
Ask AI
# ...
# You can use any provider of your choice supported by Vercel AI
OPENAI_API_KEY="YOUR_API_KEY"
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.Let us implement step-up authorization.Install the required dependencies and the Auth0 AI Components for React to get the required UI components:Report incorrect code
Copy
Ask AI
cd frontend
npm install @auth0/ai @langchain/langgraph-sdk
npx @auth0/ai-components add FederatedConnections
src/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler.tsx, with the following code:src/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler.tsx
Report incorrect code
Copy
Ask AI
import { FederatedConnectionInterrupt } from "@auth0/ai/interrupts";
import type { Interrupt } from "@langchain/langgraph-sdk";
import { EnsureAPIAccess } from "@/components/auth0-ai/FederatedConnections";
interface FederatedConnectionInterruptHandlerProps {
interrupt: Interrupt | undefined | null;
onFinish: () => void;
auth?: {
authorizePath?: string;
returnTo?: string;
};
}
export function FederatedConnectionInterruptHandler({
interrupt,
onFinish,
auth,
}: FederatedConnectionInterruptHandlerProps) {
if (
!interrupt ||
!FederatedConnectionInterrupt.isInterrupt(interrupt.value)
) {
return null;
}
return (
<div key={interrupt.ns?.join("")} className="whitespace-pre-wrap">
<EnsureAPIAccess
mode="popup"
interrupt={interrupt.value}
onFinish={onFinish}
auth={auth}
connectWidget={{
title: "Authorization Required.",
description: interrupt.value.message,
action: { label: "Authorize" },
}}
/>
</div>
);
}
src/components/chat-window.tsx file to include the FederatedConnectionInterruptHandler component:src/components/chat-window.tsx
Report incorrect code
Copy
Ask AI
//...
import { FederatedConnectionInterruptHandler } from '@/components/auth0-ai/FederatedConnections/FederatedConnectionInterruptHandler';
import { getLoginUrl } 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 && (
<FederatedConnectionInterruptHandler
auth={{
authorizePath: getLoginUrl(),
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 || [],
},
}}
onFinish={() => chat.submit(null)}
/>
)}
</div>
</>
)
}
{/* ... existing code */}
></StickyToBottomContent>
</StickToBottom>
);
}
Test your application
To test the application, start the FastAPI backend, LangGraph server, and the frontend:- In a new terminal, start the FastAPI backend:
Report incorrect code
Copy
Ask AI
cd backend
source .venv/bin/activate
fastapi dev app/main.py
- In another terminal, start the LangGraph server:
Report incorrect code
Copy
Ask AI
cd backend
source .venv/bin/activate
uv pip install -U langgraph-api
langgraph dev --port 54367 --allow-blocking
This will open the LangGraph Studio in a new tab. You can close it as we won’t
require it for testing the application.
- In another terminal, start the frontend:
Report incorrect code
Copy
Ask AI
cd frontend
cp .env.example .env # Copy the `.env.example` file to `.env`.
npm install
npm run dev
http://localhost:5173 in your browser and interact with the AI agent. 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’ve successfully integrated third-party API access using Token Vault into your LangGraph FastAPI project.Explore the example app on GitHub.Prerequisites
Before getting started, make sure you have completed the following steps:Create an Auth0 Account
To continue with this quickstart, you need to have an Auth0 account.
Create an Auth0 Application
Go to your Auth0 Dashboard to create a new Auth0 Application.
- Navigate to Applications > Applications in the left sidebar.
- Click the Create Application button in the top right.
- In the pop-up select Single Page 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:5173/auth/callback - Set Allowed Logout URLs as:
http://localhost:5173 - Set Allowed Web Origins as:
http://localhost:5173 - Click Save in the bottom right to save your changes.
Enable Token Exchange Grant
Enable the Token Exchange Grant for your Single Page Web Application. Go to Applications > [Your Application] > Settings > Advanced > Grant Types and enable the Token Exchange grant type.
Enable Refresh Token Grant
Enable the Refresh Token Grant for your Single Page Web Application. Go to Applications > [Your Application] > Settings > Advanced > Grant Types and enable the Refresh Token grant type.
Create an Auth0 API
- In your Auth0 Dashboard, go to Applications > APIs.
- Create a new API with an identifier (audience).
- Once API is created, go to the APIs Settings > Access Settings and enable Allow Offline Access.
- Note down the API identifier for your environment variables.
Create a Resource Server Client
The Resource Server Client allows your API server to perform token exchanges using access tokens instead of refresh tokens. This client enables Token Vault to exchange an access token for an external API access token (e.g., Google Calendar API).
Create this client programmatically via the Auth0 Management API:
Create this client programmatically via the Auth0 Management API:
Create Resource Server Client
Report incorrect code
Copy
Ask AI
curl -L 'https://{tenant}.auth0.com/api/v2/clients' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer {MANAGEMENT_API_TOKEN}' \
-d '{
"name": "Calendar API Resource Server Client",
"app_type": "resource_server",
"grant_types": ["urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token"],
"resource_server_identifier": "YOUR_API_IDENTIFIER"
}'- Your
MANAGEMENT_API_TOKENabove must have thecreate:clientsscope in order to create a new client. To create a new Management API token with the right access permissions: following:- Navigate to Applications > APIs > Auth0 Management API > API Explorer tab in your tenant.
- Click the Create & Authorize Test Application button.
- Copy the JWT access token shown and provide it as the
MANAGEMENT_API_TOKEN.
- Note down the
client_idandclient_secretreturned from the cURL response for your environment variables after running cURL successfully.
Configure Google Social Integration
Set up a Google developer account that allows for third-party API calls by following the Google Social Integration instructions.
OpenAI Platform
Set up an OpenAI account and API key.
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 pop-up for re-authorization.
Prepare React SPA + Hono API
Recommended: To use this example, clone the Auth0 AI JS repository:Report incorrect code
Copy
Ask AI
git clone https://github.com/auth0-lab/auth0-ai-js.git
cd auth0-ai-js/examples/calling-apis/spa-with-backend-api/react-hono-ai-sdk
Install dependencies
In the root directory of your project, 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 GenAI applicationsai: 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:
Report incorrect code
Copy
Ask AI
# 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)
.env
Report incorrect code
Copy
Ask AI
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
Server (server/.env)
.env
Report incorrect code
Copy
Ask AI
AUTH0_DOMAIN=your-auth0-domain
AUTH0_AUDIENCE=your-api-identifier
AUTH0_CLIENT_ID=your-resource-server-client-id
AUTH0_CLIENT_SECRET=your-resource-server-client-secret
OPENAI_API_KEY=your-openai-api-key
PORT=3001
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 Auth0 SPA SDK’sloginWithPopup() method to display the consent screen and allow the user to grant additional permissions.Create client/src/components/FederatedConnectionPopup.tsx:client/src/components/FederatedConnectionPopup.tsx
Report incorrect code
Copy
Ask AI
import { getAuth0Client } from "../lib/auth0";
import { Button } from "./ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import type { Auth0InterruptionUI } from "@auth0/ai-vercel/react";
interface FederatedConnectionPopupProps {
interrupt: Auth0InterruptionUI;
}
export function FederatedConnectionPopup({
interrupt,
}: FederatedConnectionPopupProps) {
const [isLoading, setIsLoading] = useState(false);
const { connection, requiredScopes, resume } = interrupt;
// Use Auth0 SPA SDK to request additional connection/scopes
const startFederatedLogin = useCallback(async () => {
try {
setIsLoading(true);
// Filter out empty scopes
const validScopes = requiredScopes.filter(
(scope: string) => scope && scope.trim() !== ""
);
const auth0Client = getAuth0Client();
// Use getTokenWithPopup for step-up authorization to request additional scopes
await auth0Client.getTokenWithPopup({
authorizationParams: {
prompt: "consent", // Required for Google Calendar scopes
connection: connection, // e.g., "google-oauth2"
connection_scope: validScopes.join(" "), // Google-specific scopes
access_type: "offline",
},
});
// The Auth0 client should automatically use the new token, but we should trigger
// a refresh to ensure the latest token is cached.
await auth0Client.getTokenSilently();
setIsLoading(false);
// Resume the interrupted tool after successful authorization
if (typeof resume === "function") {
resume();
}
} catch (error) {
setIsLoading(false);
if (typeof resume === "function") {
resume();
}
}
}, [connection, requiredScopes, 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 authorize this application.
</p>
<p className="text-xs text-yellow-600">
Required permissions:{" "}
{requiredScopes
.filter((scope: string) => scope && scope.trim() !== "")
.join(", ")}
</p>
<Button onClick={startFederatedLogin} className="w-full">
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:server/src/lib/tools/listUserCalendars.ts
Report incorrect code
Copy
Ask AI
import { tool } from "ai";
import { google } from "googleapis";
import { z } from "zod";
import { getAccessTokenForConnection } 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",
parameters: z.object({}),
execute: async () => {
// Get the access token from Token Vault using the enhanced SDK
const token = getAccessTokenForConnection();
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:server/src/lib/auth0.ts
Report incorrect code
Copy
Ask AI
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.RESOURCE_SERVER_CLIENT_ID!, // Resource server client ID for token exchange
clientSecret: process.env.RESOURCE_SERVER_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.withTokenForConnection({
accessToken: async () => accessToken,
subjectTokenType: SUBJECT_TOKEN_TYPES.SUBJECT_TYPE_ACCESS_TOKEN,
connection: process.env.GOOGLE_CONNECTION_NAME || "google-oauth2",
scopes: [
"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 Auth0 AI SDK’swithInterruptions() function wraps the Vercel AI SDK’s 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:server/src/index.ts
Report incorrect code
Copy
Ask AI
import {
createDataStreamResponse,
generateId,
streamText,
ToolExecutionError,
} 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 { InterruptionPrefix, withInterruptions } from "@auth0/ai-vercel/interrupts";
import { Auth0Interrupt } from "@auth0/ai/interrupts";
import { serve } from "@hono/node-server";
import { listUserCalendars } 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
return createDataStreamResponse({
execute: withInterruptions(
async (dataStream) => {
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.",
messages: requestMessages,
maxSteps: 5,
tools,
});
result.mergeIntoDataStream(dataStream, {
sendReasoning: true,
});
},
{ messages: requestMessages, tools }
),
onError: (error: any) => {
// Handle Auth0 AI interrupts
if (
error.cause instanceof Auth0Interrupt ||
error.cause instanceof FederatedConnectionInterrupt
) {
const serializableError = {
...error.cause.toJSON(),
toolCall: {
id: error.toolCallId,
args: error.toolArgs,
name: error.toolName,
},
};
return `${InterruptionPrefix}${JSON.stringify(serializableError)}`;
}
return "Oops! An error occurred.";
},
});
});
// 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:client/src/components/Chat.tsx
Report incorrect code
Copy
Ask AI
import { Loader2, Send, Trash2 } from "lucide-react";
import { useChat } from "@ai-sdk/react";
import { useInterruptions } from "@auth0/ai-vercel/react";
import { useAuth0 } from "../hooks/useAuth0";
import { FederatedConnectionPopup } from "./FederatedConnectionPopup";
import { Button } from "./ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { Input } from "./ui/input";
import type { Message } from "ai";
const InterruptionPrefix = "AUTH0_AI_INTERRUPTION:";
const SERVER_URL = import.meta.env.VITE_SERVER_URL;
export function Chat() {
const { getToken } = useAuth0();
const chatHelpers = useInterruptions((errorHandler) =>
useChat({
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}`,
},
});
},
})
);
const {
messages,
input,
handleInputChange,
handleSubmit,
isLoading,
error,
setMessages,
toolInterrupt,
} = chatHelpers;
// Filter out interrupted tool calls from messages
let displayMessages = messages;
if (toolInterrupt) {
displayMessages = messages.map((message) => ({
...message,
parts: message.parts?.map((part) =>
part.type === "tool-invocation" &&
part.toolInvocation.toolCallId === toolInterrupt.toolCall?.id
? {
...part,
toolInvocation: {
...part.toolInvocation,
state: "call",
},
}
: part
),
}));
}
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">
{displayMessages.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>
) : (
displayMessages.map((message) => (
<MessageBubble key={message.id} message={message} />
))
)}
{isLoading && (
<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 && !error.message.startsWith(InterruptionPrefix) && (
<div className="bg-destructive/10 text-destructive text-sm p-3 rounded-lg">
Error: {error.message}
</div>
)}
{/* Step-Up Auth Interrupt Handling */}
{toolInterrupt && (
<FederatedConnectionPopup interrupt={toolInterrupt} />
)}
{/* Input form */}
<form onSubmit={handleSubmit} className="flex gap-2">
<Input
value={input}
onChange={handleInputChange}
placeholder="Ask about your calendar..."
disabled={isLoading}
className="flex-1"
/>
<Button
className="h-10"
type="submit"
disabled={isLoading || !input.trim()}
>
<Send className="h-4 w-4" />
</Button>
</form>
</CardContent>
</Card>
);
}
function MessageBubble({ message }: { message: Message }) {
const isUser = message.role === "user";
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"
}`}
>
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
</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 Client-initiated account linking.
- Learn more about how Auth0’s Token Vault manages the tokens of supported identity providers.