Chapter 2: Connecting Your Identity Provider: Passing User Credentials into LangChain
Enterprise applications authenticate callers at the network perimeter using Identity Providers (IdPs) such as Okta, Keycloak, Auth0, or Microsoft Entra ID. Once a perimeter gateway validates a session cookie or JSON Web Token (JWT), downstream AI components must know who initiated the execution. Bridging this identity into LangChain requires an IdentityBridge to extract verified claims, map them into an immutable SecurityIdentity, and inject them into LangChain's native RunnableConfig execution configuration.
Connecting Your Identity Provider at the Perimeter
Before claims reach LangChain, the application or API gateway integrates directly with the corporate Identity Provider via standard OpenID Connect (OIDC) and OAuth 2.0 protocols:
- OIDC Discovery & JWKS Key Rotation: During startup, the perimeter service fetches the IdP's metadata from
/.well-known/openid-configurationto retrieve the JSON Web Key Set (JWKS) endpoint (jwks_uri). Public keys are cached locally and refreshed periodically to handle automated key rotation. - Cryptographic Signature Verification: Inbound HTTP requests supply an
Authorization: Bearer <token>header. The gateway or framework security filter inspects the token header for the matching key ID (kid), verifies the asymmetric signature (e.g., RS256 or ES256) using the cached public key, and validates token expiration (exp). - Standard Claims Validation: The perimeter verifies core standard claims, ensuring the token issuer (
iss) matches the trusted IdP authority and the audience (aud) matches the application client identifier. - Hand-off to the AI Application: Once cryptographic integrity is established, the raw verified claims dictionary (including subject
sub,roles,tenantId, and organizational attributes) is handed to the application'sIdentityBridgeto be transformed into execution context for LangChain.
The Role of the Identity Bridge
The IdentityBridge serves as the translation layer between web frameworks (such as FastAPI or Flask) and LangChain runtimes:
- Token & Header Extraction: Extracts verified subject claims and distributed tracing context (such as W3C
traceparentheaders) from incoming HTTP requests. - Claim Normalization: Translates enterprise claims into standardized fields for multi-tenancy, clearance ranks, and RBAC (Role-Based Access Control) groups.
- Immutable Packaging: Emits an immutable
SecurityIdentitythat downstream agents and tools cannot alter during execution. - Native Context Propagation: Attaches caller context directly to LangChain's
RunnableConfigunderconfig["configurable"]["security_identity"], avoiding fragile thread-local storage across asynchronous agent turns.
Anatomy of SecurityIdentity
Caller identity is consolidated into an immutable data structure before reaching any LangChain component:
langchain_security_extension/security_core/identity.py
class SecurityIdentity:
"""Immutable caller identity envelope containing authenticated attributes."""
def __init__(
self,
subject_id: str,
tenant_id: str,
roles: Set[str],
clearance_level: int,
department_id: str,
attributes: Dict[str, Any],
):
self._subject_id = subject_id
self._tenant_id = tenant_id
self._roles = frozenset(roles)
self._clearance_level = clearance_level
self._department_id = department_id
self._attributes = dict(attributes)
subject_id: The primary caller identifier (e.g., the JWTsubclaim or corporate email).tenant_id: The organizational boundary key, essential for preventing cross-tenant data leakage.roles: An immutable set of RBAC groups (e.g.,{"ROLE_DEVOPS_ENGINEER", "ROLE_USER"}) stored internally as afrozenset.clearance_level: A numeric rank (0 ≤ PUBLIC < 1 ≤ INTERNAL < 2 ≤ CONFIDENTIAL < 3 ≤ RESTRICTED) used for hierarchical vector chunk gating.department_id: Organizational department key used for departmental policy constraints.attributes: Validated operational tags (e.g., distributedtrace_id,span_id) consumed by policy rules and audit loggers.
Immutability Guarantee:
SecurityIdentityprotects caller integrity by encapsulating attributes behind read-only property getters and storing roles within an immutablefrozenset. Untrusted model outputs, prompt injections, or child runnables cannot mutate caller attributes or escalate roles during the execution lifecycle.
Role-to-Clearance Rank Mapping (CLEARANCE_RANK_MAP)
Enterprise Identity Providers emit role assignments as arbitrary string values or LDAP/AD group memberships (for example, ROLE_ADMIN, ROLE_DEVOPS_ENGINEER, or ROLE_USER). However, fine-grained access control in LangChain—such as vector database pre-filtering and tool parameter constraints—evaluates hierarchical data classifications using monotonic integer ranks. Example:
0 (PUBLIC / UNAUTHENTICATED) < 1 (STANDARD / INTERNAL) < 2 (SECRET / CONFIDENTIAL) < 3 (TOP_SECRET / RESTRICTED)
To bridge this difference, IdentityBridge.build_identity_from_claims() accepts a clearance_rank_map dictionary (such as CLEARANCE_RANK_MAP). The bridge inspects inbound role claims and resolves the user's active clearance rank by evaluating the maximum rank corresponding to their assigned roles (or applying a configured default_clearance). This normalizes disparate enterprise group naming schemes into consistent numeric clearance tiers across all downstream agents, retrievers, and tools.
Context Propagation via RunnableConfig
Traditional web patterns rely on session cookies or thread-local storage, both of which fail across asynchronous event loops, background worker queues, and multi-agent transfers. LangChain resolves this natively through LCEL (LangChain Expression Language) execution configs. Passing caller context under config["configurable"]["security_identity"] ensures caller identity flows deterministically into every nested prompt, tool call, and vector retriever query without relying on global variables.
During downstream execution, IdentityBridge.resolve_identity() resolves caller context through a deterministic three-tier lookup sequence: it prioritizes an explicit candidate passed in RunnableConfig, falls back to the current async task context via SecurityContextHolder (using Python contextvars), and ultimately defaults to an unprivileged SecurityIdentity.anonymous() (clearance 0, empty roles) to maintain fail-closed security.
Code Hardening: Implementing the Identity Bridge and Router Binding
When hardening your enterprise application, you bridge your perimeter authentication filter to LangChain runtimes by replacing ad-hoc claim parsing with IdentityBridge, and binding the resulting identity into your LangChain invocation configs.
1. Extracting Claims into SecurityIdentity
In your web authentication middleware or endpoint security dependency, pass the verified token claims dictionary to IdentityBridge.build_identity_from_claims(). In the reference assistant, this transformation is implemented in app/security/auth.py, replacing fragile manual role string-splitting with normalized claims translation:
app/security/auth.py
@@ -80,20 +80,18 @@ def extract_security_context_from_request(
- roles = []
- if isinstance(raw_roles, str):
- roles = [r.strip() for r in raw_roles.split(",") if r.strip()]
- elif isinstance(raw_roles, list):
- for item in raw_roles:
- if isinstance(item, str):
- roles.extend([r.strip() for r in item.split(",") if r.strip()])
+ from langchain_security_extension.security_core.identity import IdentityBridge
+ from app.security.security_integration import CLEARANCE_RANK_MAP
+
+ default_rank = CLEARANCE_RANK_MAP.get(CLEARANCE_UNAUTHENTICATED, 0)
+ identity = IdentityBridge.build_identity_from_claims(
+ claims=payload,
+ clearance_rank_map=CLEARANCE_RANK_MAP,
+ default_clearance=default_rank,
+ )
+ roles = list(identity.roles)
Explanation: The diff replaces manual string-splitting of roles with IdentityBridge.build_identity_from_claims(). The bridge extracts the caller subject, resolves the maximum clearance rank mapped from their assigned roles via CLEARANCE_RANK_MAP, and encapsulates caller claims into an immutable SecurityIdentity. You can review the full implementation in app/security/auth.py of the assistant.
Audit Telemetry on Identity Resolution: When caller identity is resolved and validated at the execution boundary (such as during
SecureRunnableentry),IdentityBridge.record_auth_success()logs an immutable, structured audit event withenforcement_point="IDENTITY_BRIDGE"andreason_code="AUTHENTICATION_SUCCESS". This captures the authenticatedsubject_id,tenant_id,roles, numericclearance_level, and distributed tracing identifiers (trace_id,span_id) to enterprise SIEM sinks before any LLM processing occurs.
2. Injecting Identity into LangChain Invocations
Once the identity is constructed, your application routes inject it into LangChain's execution graph by wrapping it inside RunnableConfig. In the reference assistant's ticket router (app/routers/tickets.py), IdentityBridge.create_runnable_config(sec_identity) generates the config dictionary and passes it to both triage and remediation agents:
app/routers/tickets.py
@@ -48,15 +54,20 @@ def submit_ticket(
if ingestion_report:
return ingestion_report
- # Triage Phase
- incident_context = triage_agent_instance.triage(request)
+ sec_identity = security_identity_from_context(sec_context)
+ run_config = IdentityBridge.create_runnable_config(sec_identity)
+
+ # Triage Phase: Injects caller identity via RunnableConfig
+ incident_context = triage_agent_instance.triage(request, config=run_config)
...
- # Remediation Phase
- report = remediation_agent_instance.process_incident(incident_context)
+ # Remediation Phase: Preserves identity across multi-agent handoff
+ report = remediation_agent_instance.process_incident(incident_context, config=run_config)
Explanation: Instead of calling agent workflows without caller context, the route creates run_config containing configurable["security_identity"]. Downstream agent entry points, retrievers, and tools automatically inherit this caller context across asynchronous tasks without thread-local dependencies. See app/routers/tickets.py in secured for the complete controller logic.
Verification Tests (Guardrail Tests)
Validate identity claim extraction, context propagation, distributed trace binding, and cache eviction upon credential revocation using the project's standardized guardrail suite:
-
Test 20: Identity Context Propagation (
20-framework-starter-identity-propagation.sh)
Validates thatIdentityBridgeextracts caller roles, tenant identifiers, and clearance ranks from perimeter claims, constructs the immutableSecurityIdentity, populatesRunnableConfig["configurable"]["security_identity"], and verifies thatIdentityBridge.record_auth_success()emits an auditableAUTHENTICATION_SUCCESStelemetry event.BASH./internal-security-guardrail-tests/20-framework-starter-identity-propagation.sh -
Test 22: PDP Cache Eviction on Identity Invalidation (
22-pdp-cache-eviction-on-identity-invalidation.sh)
Verifies that when caller credentials, roles, or clearance levels are revoked or modified in the user directory or Identity Provider,OPACacheInvalidationHandlerimmediately evicts cached authorization verdicts from the two-tier PDP policy decision cache (L1/L2). This guarantees that revoked privileges cannot be exercised using stale cache entries.BASH./internal-security-guardrail-tests/22-pdp-cache-eviction-on-identity-invalidation.sh -
Test 27: OpenTelemetry Trace & Span Correlation (
27-opentelemetry-trace-span-correlation.sh)
Verifies that inbound W3Ctraceparentheaders (formatted asversion-trace_id-span_id-flags) are parsed at the request boundary, attached toSecurityIdentity.attributesviawith_trace_context(trace_id, span_id), and propagated into downstream audit records emitted byOpenTelemetryLogsPublisherfor end-to-end distributed tracing.BASH./internal-security-guardrail-tests/27-opentelemetry-trace-span-correlation.sh
Framework Integration & Best Practices
- Perimeter Cryptographic Validation: Always verify token signatures and expiration timestamps at your gateway or web framework middleware before invoking
IdentityBridge. Never construct an identity from unverified claims. - Mandatory Multi-Tenancy: Enforce the presence of
tenant_idon every incoming request to prevent accidental cross-tenant document and tool access. - Explicit Propagation: Always pass
config=configto entry points, child runnables, and manual tool calls so caller identity is never dropped mid-execution. - Fail-Early Rejection: If a token lacks a valid subject (
sub) or tenant identifier, reject the request with HTTP 401/403 immediately before initializing LLM chains or consuming model tokens. - Immediate Cache Invalidation on Revocation: Coordinate user role revocation events between your identity directory and the LangChain PDP cache to prevent stale permissions from persisting.