Skip to main content

Connect an AI Agent with LangChain

Use this guide to connect a LangChain agent to ThunderID using its own identity or acting on behalf of a user. Choose a mode to see its setup, code, and expected result.

Your agent will list the available courses using its own permissions. No student signs in.

Prerequisites​

Select Python or TypeScript in a code block. Your choice applies throughout the page. Each example runs one request with one available tool, then exits.

1

Run ThunderID

Start a local instance, or use one you already have:

$npx thunderid

Requires Node.js 18+

Full install guide →

Open the Console at https://localhost:8090/console.

2

Register an Agent

  1. In the Console, open Agents and click Add agent.
  2. Name it Course Agent. Select Gemini as the Model Provider, enter gemini-flash-lite-latest as the Model, and select an Owner. Complete the wizard to create the agent.
  3. Copy the Client Secret when shown, then the Client ID from Overview → Agent details. Keep them for .env; the secret appears only once.
3

Configure Access

In Resource servers, create an API resource server named Course Catalog with Identifier https://courses.example.com and permission delimiter :. On Resources, create Courses with handle courses.

Create the following action under Courses. In Roles, create the role with that permission, then assign it on Assignments → Add assignment:

Action nameHandlePermissionRoleAssign to
List coursesreadcourses:readCourse ReaderAgent → Course Agent

Reuse these resources if they already exist. The resource identifier names the local course tools; you do not need a service at that URL.

4

Create the Project

bash
mkdir langchain-quickstart
cd langchain-quickstart
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install langchain==1.4.2 langchain-google-genai==4.4.0 requests==2.34.2 python-dotenv==1.2.3

On Windows, use py -3.12 -m venv .venv and activate with .venv\Scripts\Activate.ps1 in PowerShell.

Create .env in this directory. Replace the credential placeholders:

.env
dotenv
THUNDERID_BASE_URL=https://localhost:8090
AGENT_CLIENT_ID=replace-with-your-client-id
AGENT_SECRET=replace-with-your-client-secret
GOOGLE_API_KEY=replace-with-your-google-api-key
GEMINI_MODEL=gemini-flash-lite-latest
COURSE_RESOURCE=https://courses.example.com
THUNDERID_CA_BUNDLE=./thunderid.pem

Keep .env out of version control. Use your instance's HTTPS URL for THUNDERID_BASE_URL, and the resource server's Identifier for COURSE_RESOURCE.

For a local instance, save its public certificate beside .env:

bash
openssl s_client -connect localhost:8090 -servername localhost </dev/null 2>/dev/null | openssl x509 -out thunderid.pem

You can also copy config/certs/server.cert from your installation directory and rename it to thunderid.pem. For a publicly trusted certificate, omit THUNDERID_CA_BUNDLE.

5

Build the Agent

The list_courses tool obtains an agent token, validates it with ThunderID, and checks courses:read before returning the catalog.

ThunderID
User
AI
Agent
🛠️ Tool
Validate
List courses
1asks
2get token
3access token
4calls tool

Save the complete sample beside .env:

Copy agent_own.py
agent_own.py
python
import base64
import json
import os
from urllib.parse import quote_plus

import requests
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_google_genai import ChatGoogleGenerativeAI


load_dotenv()
BASE_URL = os.environ["THUNDERID_BASE_URL"].rstrip("/")
CLIENT_ID = os.environ["AGENT_CLIENT_ID"]
CLIENT_SECRET = os.environ["AGENT_SECRET"]
RESOURCE = os.environ["COURSE_RESOURCE"]


def post(path, data):
credentials = f"{quote_plus(CLIENT_ID)}:{quote_plus(CLIENT_SECRET)}"
basic = base64.b64encode(credentials.encode()).decode()
response = requests.post(
f"{BASE_URL}{path}",
data=data,
headers={"Authorization": f"Basic {basic}"},
verify=os.environ.get("THUNDERID_CA_BUNDLE") or True,
timeout=30,
allow_redirects=False,
)
if response.status_code != 200:
raise RuntimeError(f"{path} returned HTTP {response.status_code}; check the agent configuration.")
return response.json()


def validate_token(token: str, permission: str) -> dict:
identity = post("/oauth2/introspect", {"token": token})
audiences = identity.get("aud", [])
if isinstance(audiences, str):
audiences = [audiences]
if (identity.get("active") is not True
or identity.get("client_id") != CLIENT_ID
or not identity.get("sub")
or RESOURCE not in audiences
or permission not in identity.get("scope", "").split()):
raise PermissionError(f"The token is not valid for {permission} on {RESOURCE}.")
return identity


def read_courses(token: str) -> dict:
identity = validate_token(token, "courses:read")
print("Verified agent:", identity["sub"], flush=True)
result = {"courses": [
{"id": "CS101", "name": "Introduction to Computer Science"},
{"id": "CS205", "name": "Data Structures"},
]}
print("Tool result:", json.dumps(result), flush=True)
return result


@tool
def list_courses() -> dict:
"""List the available courses using the agent's own identity."""
token = post("/oauth2/token", {
"grant_type": "client_credentials", "resource": RESOURCE, "scope": "courses:read",
})["access_token"]
return read_courses(token)


if __name__ == "__main__":
agent = create_agent(
model=ChatGoogleGenerativeAI(model=os.environ["GEMINI_MODEL"], temperature=0),
tools=[list_courses],
system_prompt="Use list_courses to get the catalog. Report only the tool result. If it fails, report the error without retrying.",
)
print("You: List the available courses.", flush=True)
response = agent.invoke({"messages": [{"role": "user", "content": "List the available courses."}]})
print("Agent:", response["messages"][-1].text)

Tokens stay inside the tool implementation. LangChain exposes only the tool's business arguments to the model.

6

Run the Agent

bash
python agent_own.py

Expect a result like this:

text
You: List the available courses.
Verified agent: <agent-id>
Tool result: {"courses":[{"id":"CS101","name":"Introduction to Computer Science"},{"id":"CS205","name":"Data Structures"}]}
Agent: The available courses are CS101 and CS205.

Verified agent should match the Agent ID in the Console. The tool used the agent's permissions without student sign-in.

The agent's wording and JSON spacing can vary. The Tool result line confirms the operation completed.

Resolve common errors
SymptomCheck
HTTP 401 from token or introspection requestsUse the agent's current Client ID and secret. On Advanced, confirm Client authentication method is client_secret_basic.
invalid_targetMatch COURSE_RESOURCE to the resource server's Identifier.
Certificate verification failsExport the running instance's certificate. For Python, set THUNDERID_CA_BUNDLE to that file. For TypeScript, set NODE_EXTRA_CA_CERTS before starting the process.
Gemini reports a model or quota errorCheck GEMINI_MODEL, your API key, and your Google project quota.
SymptomCheck
Token is not valid for courses:readAssign Course Reader to the agent.

What's Next​

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.