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:


The Role of the Identity Bridge

The IdentityBridge serves as the translation layer between web frameworks (such as FastAPI or Flask) and LangChain runtimes:


Anatomy of SecurityIdentity

Caller identity is consolidated into an immutable data structure before reaching any LangChain component:

langchain_security_extension/security_core/identity.py

SECURITY LIBRARY SNIPPET (INFORMATIVE) langchain_security_extension/security_core/identity.py
PYTHON
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)

Immutability Guarantee: SecurityIdentity protects caller integrity by encapsulating attributes behind read-only property getters and storing roles within an immutable frozenset. 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

DEMO APP (DIFF) app/security/auth.py
DIFF
@@ -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 SecureRunnable entry), IdentityBridge.record_auth_success() logs an immutable, structured audit event with enforcement_point="IDENTITY_BRIDGE" and reason_code="AUTHENTICATION_SUCCESS". This captures the authenticated subject_id, tenant_id, roles, numeric clearance_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

DEMO APP (DIFF) app/routers/tickets.py
DIFF
@@ -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:


Framework Integration & Best Practices