Skip to main content

What you’ll learn

  • Authenticate with Auth0 AI: configure Auth0 AI and obtain federated access tokens for Slack APIs.
  • Call external APIs securely: use the token to list Slack channels through a LangChain tool.
  • Handle auth interruptions: enable dynamic user consent and reauthorization within chat flows.
Before getting started, make sure you have completed the following steps:
1

Create an Auth0 Account

To continue with this quickstart, you need to have an Auth0 account.
2

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.
3

Configure Google Social Integration

Set up a Google developer account that allows for third-party API calls by following the Google Social Integration instructions.
4

OpenAI Platform

  • https://mintlify.s3.us-west-1.amazonaws.com/auth0-genai-feat-add-diagrams-modal-with-steps/img/nextjs-light.svg Next.js
  • https://mintlify.s3.us-west-1.amazonaws.com/auth0-genai-feat-add-diagrams-modal-with-steps/img/fastapi-light.svg FastAPI

Getting started using AI

To get started quickly:
Clone auth0-lab/auth0-ai-js and navigate to examples/calling-apis/chatbot/app/(langgraph) directory.
Then, integrate Auth0 AI docs into your preferred AI tool:

  • VS Code
  • Cursor
  • Claude Code
  • Claude
Create a .vscode/mcp.json file and add:
{
  "servers": {
    "Auth0 for AI Agents": {
      "type": "http",
      "url": "https://auth0.com/ai/docs/mcp"
    }
  }
}
To learn more, read the VS Code documentation.

or Follow manual steps

1

Configure Auth0 AI

First, you must install the SDK:
npm install @auth0/ai-langchain
Then, you need to initialize Auth0 AI and set up the connection to request access tokens with the required Slack scopes.
./src/lib/auth0-ai.ts
import { SUBJECT_TOKEN_TYPES } from "@auth0/ai";
import { Auth0AI } from "@auth0/ai-langchain";

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,
  });

export const withSlack = withAccessTokenForConnection("sign-in-with-slack", ["channels:read", "groups:read"]);

2

Integrate your tool with Slack

Wrap your tool using the Auth0 AI SDK to obtain an access token for the Slack API.
./src/lib/tools/listChannels.ts
import { ErrorCode, WebClient } from "@slack/web-api";
import { getAccessTokenFromTokenVault } from "@auth0/ai-langchain";
import { TokenVaultError } from "@auth0/ai/interrupts";
import { withSlack } from "@/lib/auth0-ai";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

export const listChannels = withSlack(
  tool(async ({ date }) => {
    // Get the access token from Auth0 AI
    const accessToken = getAccessTokenFromTokenVault();

    // Slack SDK
    try {
      const web = new WebClient(accessToken);

      const result = await web.conversations.list({
        exclude_archived: true,
        types: "public_channel,private_channel",
        limit: 10,
      });

      return result.channels?.map((channel) => channel.name);
    } catch (error) {
      if (error && typeof error === "object" && "code" in error) {
        if (error.code === ErrorCode.HTTPError) {
          throw new TokenVaultError(
            `Authorization required to access the Token Vault connection`
          );
        }
      }

      throw error;
    }
  },
  {
    name: "list_slack_channels",
    description: "List channels for the current user on Slack",
    schema: z.object({
      date: z.coerce.date(),
    }),
  })
);
Now that the tool is protected, you can pass it your LangGraph agent as part of a ToolNode. The agent will automatically request the access token when the tool is called.
src/lib/agent.ts
import { AIMessage } from "@langchain/core/messages";
import { RunnableLike } from "@langchain/core/runnables";
import { END, InMemoryStore, MemorySaver, MessagesAnnotation, START, StateGraph } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";

import { listChannels } from "@/lib/tools/listChannels";

const model = new ChatOpenAI({ model: "gpt-4o", }).bindTools([
  listChannels,
]);

const callLLM = async (state: typeof MessagesAnnotation.State) => {
  const response = await model.invoke(state.messages);
  return { messages: [response] };
};

const routeAfterLLM: RunnableLike = function (state) {
  const lastMessage = state.messages[state.messages.length - 1] as AIMessage;
  if (!lastMessage.tool_calls?.length) {
    return END;
  }
  return "tools";
};

const stateGraph = new StateGraph(MessagesAnnotation)
  .addNode("callLLM", callLLM)
  .addNode(
    "tools",
    new ToolNode(
      [
        // A tool with Token Vault access
        listChannels,
        // ... other tools
      ],
      {
        // Error handler should be disabled in order to
        // trigger interruptions from within tools.
        handleToolErrors: false,
      }
    )
  )
  .addEdge(START, "callLLM")
  .addConditionalEdges("callLLM", routeAfterLLM, [END, "tools"])
  .addEdge("tools", "callLLM");

const checkpointer = new MemorySaver();
const store = new InMemoryStore();

export const graph = stateGraph.compile({
  checkpointer,
  store,
  interruptBefore: [],
  interruptAfter: [],
});
3

Handle authentication redirects

Interrupts are a way for the system to pause execution and prompt the user to take an action —such as authenticating or granting API access— before resuming the interaction. This ensures that any required access is granted dynamically and securely during the chat experience. In this context, Auth0-AI SDK manages such authentication redirects integrated with the Langchain SDK.

Server Side

On the server side of your Next.js application you need to set up a route to handle the Chat API requests. This route will be responsible for forwarding the requests to the LangGraph API. Additionally, you must provide the accessToken in the headers.
./src/app/api/langgraph/[..._path]/route.ts
import { initApiPassthrough } from "langgraph-nextjs-api-passthrough";
import { NextRequest } from "next/server";

import { auth0 } from "@/lib/auth0";

async function getAccessToken() {
  const tokenResult = await auth0.getAccessToken();
  if (!tokenResult?.token) {
    throw new Error("Error retrieving access token for langgraph api.");
  }
  return tokenResult.token;
}

export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } =
  initApiPassthrough({
    apiUrl: process.env.LANGGRAPH_API_URL,
    apiKey: process.env.LANGSMITH_API_KEY,
    runtime: "edge",
    baseRoute: "langgraph/",
    headers: async (req: NextRequest) => {
      const headers: Record<string, string> = {};
      req.headers.forEach((value, key) => {
        headers[key] = value;
      });

      const accessToken = await getAccessToken();
      headers["Authorization"] = `Bearer ${accessToken}`;
      return headers;
    },
  });


Here, the property auth0 is an instance of @auth0/nextjs-auth0 to handle the application auth flows.
You can check different authentication options for Next.js with Auth0 at the official documentation.

Add Custom Authentication

For more information on how to add custom authentication for your LangGraph Platform application, see the Custom Auth guide.
In your langgraph.json, add the path to your auth file:
langgraph.json
{
  "node_version": "20",
  "graphs": {
    "agent": "./src/lib/agent.ts:agent"
  },
  "env": ".env",
  "auth": {
    "path": "./src/lib/auth.ts:authHandler"
  }
}
Then, in your auth.ts file, add your auth logic:
src/lib/auth.ts
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 };

Client Side

In this example, we utilize the TokenVaultConsentPopup component to show a pop-up that allows the user to authenticate with Slack and grant access with the requested scopes. You’ll first need to install the @auth0/ai-components package:
npx @auth0/ai-components add TokenVault
Then, you can integrate the authentication popup in your chat component, using the interruptions helper from the SDK:
./src/components/chat.tsx
import { useStream } from "@langchain/langgraph-sdk/react";
import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
import { TokenVaultConsentPopup } from "@/components/auth0-ai/TokenVault/popup";

const useFocus = () => {
  const htmlElRef = useRef<HTMLInputElement>(null);
  const setFocus = () => {
    if (!htmlElRef.current) {
      return;
    }
    htmlElRef.current.focus();
  };
  return [htmlElRef, setFocus] as const;
};

export default function Chat() {
  const [threadId, setThreadId] = useQueryState("threadId");
  const [input, setInput] = useState("");
  const thread = useStream({
    apiUrl: `${process.env.NEXT_PUBLIC_URL}/api/langgraph`,
    assistantId: "agent",
    threadId,
    onThreadId: setThreadId,
    onError: (err) => {
      console.dir(err);
    },
  });

  const [inputRef, setInputFocus] = useFocus();
  useEffect(() => {
    if (thread.isLoading) {
      return;
    }
    setInputFocus();
  }, [thread.isLoading, setInputFocus]);

  const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
    e.preventDefault();
    thread.submit(
      { messages: [{ type: "human", content: input }] },
      {
        optimisticValues: (prev) => ({
          messages: [
            ...((prev?.messages as []) ?? []),
            { type: "human", content: input, id: "temp" },
          ],
        }),
      }
    );
    setInput("");
  };

  return (
    <div>
      {thread.messages.filter((m) => m.content && ["human", "ai"].includes(m.type)).map((message) => (
        <div key={message.id}>
          {message.type === "human" ? "User: " : "AI: "}
          {message.content as string}
        </div>
      ))}

      {thread.interrupt && TokenVaultInterrupt.isInterrupt(thread.interrupt.value) ? (
        <div key={thread.interrupt.ns?.join("")}>
          <TokenVaultConsentPopup
            interrupt={thread.interrupt.value}
            onFinish={() => thread.submit(null)}
            connectWidget={{
                title: "List Slack channels",
                description:"description ...",
                action: { label: "Check" },
              }}
          />
        </div>
      ) : null}

      <form onSubmit={handleSubmit}>
        <input ref={inputRef} value={input} placeholder="Say something..." readOnly={thread.isLoading} disabled={thread.isLoading} onChange={(e) => setInput(e.target.value)} />
      </form>
    </div>
  );
}

Account Linking

If you're integrating with Slack, but users in your app or agent can sign in using other methods (e.g., a username and password or another social provider), you'll need to link these identities into a single user account. Auth0 refers to this process as Account Linking.Account Linking logic and handling will vary depending on your app or agent. You can find an example of how to implement it in a Next.js chatbot app here. If you have questions or are looking for best practices, join our Discord and ask in the #auth0-for-gen-ai channel.

How-Tos