CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langchain--langgraph-sdk

TypeScript/JavaScript SDK for interacting with the LangGraph REST API server

Overview
Eval results
Files

auth.mddocs/

Authentication

The Auth system provides flexible authentication and authorization for LangGraph applications. It supports custom authentication workflows, event handling, and HTTP exception management for secure application development. The system is designed to integrate seamlessly with various authentication providers and custom authorization logic.

Core Functionality

The Auth system supports:

  • Custom Authentication: Implement custom authentication logic with flexible callbacks
  • Event Handling: Register callbacks for various authentication and authorization events
  • HTTP Exceptions: Structured error handling with standard HTTP status codes
  • User Management: Type-safe user handling with extensible user models
  • Filter Integration: Authentication filters for fine-grained access control

Auth Class API

class Auth<TExtra = {}, TAuthReturn extends BaseAuthReturn = BaseAuthReturn, TUser extends BaseUser = ToUserLike<TAuthReturn>> {
  /**
   * Set the authentication callback function
   * @param cb - Authentication callback that validates and returns user information
   * @returns Auth instance with updated authentication type
   */
  authenticate<T extends BaseAuthReturn>(cb: AuthenticateCallback<T>): Auth<TExtra, T>;

  /**
   * Register event callback handlers for authentication events
   * @param event - Event type to handle
   * @param callback - Callback function for the event
   * @returns This Auth instance for method chaining
   */
  on<T extends CallbackEvent>(event: T, callback: OnCallback<T, TUser>): this;
}

HTTPException Class

class HTTPException extends Error {
  /** HTTP status code */
  status: number;

  /** Error message */
  message: string;

  /**
   * Create HTTP exception with status code
   * @param message - Error message
   * @param status - HTTP status code
   */
  constructor(message: string, status: number);
}

Core Types

Authentication Types

interface BaseAuthReturn {
  /** Unique user identifier */
  sub: string;
  /** Additional user data */
  [key: string]: any;
}

interface BaseUser {
  /** User identifier */
  id: string;
  /** Additional user properties */
  [key: string]: any;
}

type ToUserLike<T extends BaseAuthReturn> = {
  id: T["sub"];
} & Omit<T, "sub">;

type AuthenticateCallback<T extends BaseAuthReturn> = (
  request: AuthRequest
) => Promise<T> | T;

interface AuthRequest {
  /** Request headers */
  headers: Record<string, string>;
  /** Request query parameters */
  query: Record<string, string>;
  /** Request body data */
  body?: any;
  /** Request method */
  method: string;
  /** Request URL */
  url: string;
  /** Request cookies */
  cookies: Record<string, string>;
}

Event System

type CallbackEvent =
  | "beforeAuth"
  | "afterAuth"
  | "authError"
  | "beforeAccess"
  | "afterAccess"
  | "accessDenied"
  | "sessionStart"
  | "sessionEnd";

type OnCallback<T extends CallbackEvent, TUser extends BaseUser> = T extends "beforeAuth"
  ? (request: AuthRequest) => Promise<void> | void
  : T extends "afterAuth"
  ? (user: TUser, request: AuthRequest) => Promise<void> | void
  : T extends "authError"
  ? (error: Error, request: AuthRequest) => Promise<void> | void
  : T extends "beforeAccess"
  ? (user: TUser, resource: string, action: string) => Promise<boolean> | boolean
  : T extends "afterAccess"
  ? (user: TUser, resource: string, action: string, granted: boolean) => Promise<void> | void
  : T extends "accessDenied"
  ? (user: TUser, resource: string, action: string) => Promise<void> | void
  : T extends "sessionStart"
  ? (user: TUser, sessionId: string) => Promise<void> | void
  : T extends "sessionEnd"
  ? (user: TUser, sessionId: string) => Promise<void> | void
  : never;

Filter Types

interface AuthFilters {
  /** User ID filter */
  userId?: string | string[];
  /** Role-based filter */
  roles?: string | string[];
  /** Permission-based filter */
  permissions?: string | string[];
  /** Resource access filter */
  resources?: string | string[];
  /** Time-based access filter */
  timeRange?: {
    start?: string;
    end?: string;
  };
  /** Custom filter predicates */
  custom?: Record<string, any>;
}

interface AuthEventValueMap {
  beforeAuth: { request: AuthRequest };
  afterAuth: { user: BaseUser; request: AuthRequest };
  authError: { error: Error; request: AuthRequest };
  beforeAccess: { user: BaseUser; resource: string; action: string };
  afterAccess: { user: BaseUser; resource: string; action: string; granted: boolean };
  accessDenied: { user: BaseUser; resource: string; action: string };
  sessionStart: { user: BaseUser; sessionId: string };
  sessionEnd: { user: BaseUser; sessionId: string };
}

Usage Examples

Basic Authentication Setup

import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";

// Define custom user type
interface CustomUser {
  id: string;
  email: string;
  role: string;
  permissions: string[];
  organizationId: string;
}

// Create auth instance with type safety
const auth = new Auth<{}, { sub: string; email: string; role: string; permissions: string[]; organizationId: string }, CustomUser>();

// Set authentication callback
auth.authenticate(async (request) => {
  const authHeader = request.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    throw new HTTPException("Missing or invalid authorization header", 401);
  }

  const token = authHeader.substring(7); // Remove 'Bearer ' prefix

  try {
    // Validate token (example with JWT)
    const decoded = await validateJWT(token);

    // Fetch user data
    const user = await getUserFromDatabase(decoded.sub);

    if (!user) {
      throw new HTTPException("User not found", 404);
    }

    if (!user.active) {
      throw new HTTPException("User account is deactivated", 403);
    }

    return {
      sub: user.id,
      email: user.email,
      role: user.role,
      permissions: user.permissions,
      organizationId: user.organizationId
    };

  } catch (error) {
    if (error instanceof HTTPException) {
      throw error;
    }
    throw new HTTPException("Invalid token", 401);
  }
});

// Example JWT validation function
async function validateJWT(token: string) {
  // Implementation depends on your JWT library
  // Return decoded payload or throw error
  const decoded = jwt.verify(token, process.env.JWT_SECRET!);
  return decoded as { sub: string; exp: number; iat: number };
}

// Example user database lookup
async function getUserFromDatabase(userId: string) {
  // Your database query logic
  return {
    id: userId,
    email: "user@example.com",
    role: "user",
    permissions: ["read", "write"],
    organizationId: "org_123",
    active: true
  };
}

Event Handling and Monitoring

// Register authentication event handlers
auth.on("beforeAuth", async (request) => {
  console.log(`Authentication attempt from ${request.headers['x-forwarded-for'] || 'unknown'}`);

  // Rate limiting check
  await checkRateLimit(request.headers['x-forwarded-for']);
});

auth.on("afterAuth", async (user, request) => {
  console.log(`User ${user.email} authenticated successfully`);

  // Log successful authentication
  await logAuthEvent({
    userId: user.id,
    event: 'login_success',
    timestamp: new Date().toISOString(),
    ip: request.headers['x-forwarded-for'],
    userAgent: request.headers['user-agent']
  });

  // Update last login timestamp
  await updateLastLogin(user.id);
});

auth.on("authError", async (error, request) => {
  console.error(`Authentication failed: ${error.message}`);

  // Log failed authentication attempt
  await logAuthEvent({
    event: 'login_failed',
    error: error.message,
    timestamp: new Date().toISOString(),
    ip: request.headers['x-forwarded-for'],
    userAgent: request.headers['user-agent']
  });

  // Check for suspicious activity
  await checkSuspiciousActivity(request.headers['x-forwarded-for']);
});

// Session management
auth.on("sessionStart", async (user, sessionId) => {
  console.log(`Session started for user ${user.email}: ${sessionId}`);

  await createSessionRecord({
    sessionId,
    userId: user.id,
    startTime: new Date().toISOString(),
    active: true
  });
});

auth.on("sessionEnd", async (user, sessionId) => {
  console.log(`Session ended for user ${user.email}: ${sessionId}`);

  await updateSessionRecord(sessionId, {
    endTime: new Date().toISOString(),
    active: false
  });
});

Access Control and Authorization

// Role-based access control
auth.on("beforeAccess", async (user, resource, action) => {
  console.log(`Access check: ${user.email} wants to ${action} ${resource}`);

  // Admin users have full access
  if (user.role === 'admin') {
    return true;
  }

  // Check resource-specific permissions
  const hasPermission = await checkResourcePermission(user.id, resource, action);

  if (!hasPermission) {
    console.warn(`Access denied: ${user.email} cannot ${action} ${resource}`);
    return false;
  }

  // Organization-based access control
  const resourceOrg = await getResourceOrganization(resource);
  if (resourceOrg && resourceOrg !== user.organizationId) {
    console.warn(`Cross-org access denied: ${user.email} cannot access ${resource}`);
    return false;
  }

  return true;
});

auth.on("afterAccess", async (user, resource, action, granted) => {
  // Log access attempt
  await logAccessEvent({
    userId: user.id,
    resource,
    action,
    granted,
    timestamp: new Date().toISOString()
  });

  if (granted) {
    console.log(`Access granted: ${user.email} can ${action} ${resource}`);
  }
});

auth.on("accessDenied", async (user, resource, action) => {
  console.warn(`Access denied: ${user.email} cannot ${action} ${resource}`);

  // Alert on sensitive resource access attempts
  if (isSensitiveResource(resource)) {
    await sendSecurityAlert({
      userId: user.id,
      resource,
      action,
      timestamp: new Date().toISOString(),
      severity: 'high'
    });
  }
});

// Helper functions for access control
async function checkResourcePermission(userId: string, resource: string, action: string): Promise<boolean> {
  // Check database for user permissions on specific resource
  const permissions = await getUserResourcePermissions(userId, resource);
  return permissions.includes(action) || permissions.includes('*');
}

async function getResourceOrganization(resource: string): Promise<string | null> {
  // Get the organization that owns the resource
  const resourceData = await getResourceMetadata(resource);
  return resourceData?.organizationId || null;
}

function isSensitiveResource(resource: string): boolean {
  const sensitivePatterns = ['/admin/', '/financial/', '/personal-data/'];
  return sensitivePatterns.some(pattern => resource.includes(pattern));
}

Multi-Provider Authentication

// Support multiple authentication providers
interface AuthProvider {
  name: string;
  validate: (request: AuthRequest) => Promise<BaseAuthReturn>;
}

const providers: AuthProvider[] = [
  {
    name: 'jwt',
    validate: async (request) => {
      const token = extractBearerToken(request);
      const decoded = await validateJWT(token);
      return await getUserFromJWT(decoded);
    }
  },
  {
    name: 'api_key',
    validate: async (request) => {
      const apiKey = request.headers['x-api-key'];
      if (!apiKey) throw new HTTPException("API key required", 401);
      return await getUserFromApiKey(apiKey);
    }
  },
  {
    name: 'session',
    validate: async (request) => {
      const sessionId = request.cookies['session_id'];
      if (!sessionId) throw new HTTPException("Session required", 401);
      return await getUserFromSession(sessionId);
    }
  }
];

// Multi-provider authentication
auth.authenticate(async (request) => {
  let lastError: Error | null = null;

  // Try each provider in order
  for (const provider of providers) {
    try {
      console.log(`Trying authentication with provider: ${provider.name}`);
      const result = await provider.validate(request);
      console.log(`Authentication successful with provider: ${provider.name}`);
      return result;
    } catch (error) {
      console.log(`Provider ${provider.name} failed: ${error.message}`);
      lastError = error;
      continue;
    }
  }

  // All providers failed
  throw lastError || new HTTPException("Authentication failed", 401);
});

// Provider-specific implementations
function extractBearerToken(request: AuthRequest): string {
  const authHeader = request.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    throw new HTTPException("Bearer token required", 401);
  }
  return authHeader.substring(7);
}

async function getUserFromApiKey(apiKey: string) {
  const keyData = await validateApiKey(apiKey);
  if (!keyData) {
    throw new HTTPException("Invalid API key", 401);
  }
  return {
    sub: keyData.userId,
    email: keyData.email,
    role: keyData.role,
    permissions: keyData.permissions,
    organizationId: keyData.organizationId
  };
}

async function getUserFromSession(sessionId: string) {
  const session = await getActiveSession(sessionId);
  if (!session) {
    throw new HTTPException("Invalid or expired session", 401);
  }
  return {
    sub: session.userId,
    email: session.userEmail,
    role: session.userRole,
    permissions: session.permissions,
    organizationId: session.organizationId
  };
}

Error Handling and Security

// Comprehensive error handling
class AuthService {
  private auth: Auth;
  private securityLogger: SecurityLogger;

  constructor() {
    this.auth = new Auth();
    this.securityLogger = new SecurityLogger();
    this.setupAuth();
  }

  private setupAuth() {
    this.auth.authenticate(async (request) => {
      try {
        // Pre-authentication security checks
        await this.performSecurityChecks(request);

        // Main authentication logic
        const authResult = await this.performAuthentication(request);

        // Post-authentication validation
        await this.validateAuthResult(authResult);

        return authResult;

      } catch (error) {
        await this.handleAuthError(error, request);
        throw error;
      }
    });

    // Error recovery and monitoring
    this.auth.on("authError", async (error, request) => {
      await this.securityLogger.logAuthFailure({
        error: error.message,
        ip: request.headers['x-forwarded-for'],
        userAgent: request.headers['user-agent'],
        timestamp: new Date().toISOString(),
        endpoint: request.url
      });

      // Implement progressive delays for repeated failures
      await this.implementRateLimit(request.headers['x-forwarded-for']);
    });
  }

  private async performSecurityChecks(request: AuthRequest) {
    // IP whitelist/blacklist check
    const clientIP = request.headers['x-forwarded-for'];
    if (await this.isBlacklistedIP(clientIP)) {
      throw new HTTPException("Access denied", 403);
    }

    // Request validation
    if (!this.isValidAuthRequest(request)) {
      throw new HTTPException("Invalid request format", 400);
    }

    // Rate limiting
    if (await this.isRateLimited(clientIP)) {
      throw new HTTPException("Too many requests", 429);
    }
  }

  private async performAuthentication(request: AuthRequest): Promise<BaseAuthReturn> {
    // Determine authentication method
    if (request.headers.authorization?.startsWith('Bearer ')) {
      return await this.authenticateJWT(request);
    } else if (request.headers['x-api-key']) {
      return await this.authenticateApiKey(request);
    } else if (request.cookies['session_id']) {
      return await this.authenticateSession(request);
    } else {
      throw new HTTPException("No valid authentication method provided", 401);
    }
  }

  private async handleAuthError(error: Error, request: AuthRequest) {
    if (error instanceof HTTPException) {
      // Log specific HTTP exceptions
      await this.securityLogger.logHTTPException(error, request);

      // Check for attack patterns
      if (this.isLikelyAttack(error, request)) {
        await this.triggerSecurityAlert(error, request);
      }
    } else {
      // Log unexpected errors
      await this.securityLogger.logUnexpectedError(error, request);
    }
  }

  private isLikelyAttack(error: HTTPException, request: AuthRequest): boolean {
    // Heuristics for detecting potential attacks
    const suspiciousPatterns = [
      /sql/i,
      /script/i,
      /<script>/i,
      /union.*select/i,
      /etc\/passwd/i
    ];

    const requestContent = JSON.stringify(request);
    return suspiciousPatterns.some(pattern => pattern.test(requestContent));
  }

  private async triggerSecurityAlert(error: Error, request: AuthRequest) {
    await this.securityLogger.triggerAlert({
      type: 'potential_attack',
      error: error.message,
      request: {
        ip: request.headers['x-forwarded-for'],
        userAgent: request.headers['user-agent'],
        url: request.url,
        method: request.method
      },
      timestamp: new Date().toISOString(),
      severity: 'high'
    });
  }
}

// Security logging utility
class SecurityLogger {
  async logAuthFailure(event: any) {
    // Log to security monitoring system
    console.error("Auth failure:", event);
  }

  async logHTTPException(error: HTTPException, request: AuthRequest) {
    console.warn(`HTTP ${error.status}: ${error.message}`, {
      ip: request.headers['x-forwarded-for'],
      url: request.url
    });
  }

  async triggerAlert(alert: any) {
    // Send to security team
    console.error("SECURITY ALERT:", alert);
  }
}

Integration with LangGraph Client

// Use auth with LangGraph Client
import { Client } from "@langchain/langgraph-sdk";
import { Auth } from "@langchain/langgraph-sdk/auth";

// Create authenticated client
const auth = new Auth();
auth.authenticate(async (request) => {
  // Your authentication logic
  return await authenticateUser(request);
});

// Custom client with auth integration
class AuthenticatedLangGraphClient extends Client {
  private auth: Auth;

  constructor(config: ClientConfig & { auth: Auth }) {
    const { auth, ...clientConfig } = config;

    super({
      ...clientConfig,
      onRequest: async (url, init) => {
        // Add authentication to requests
        const authRequest = this.convertToAuthRequest(url, init);
        const user = await auth.authenticate(authRequest);

        // Add auth headers
        return {
          ...init,
          headers: {
            ...init.headers,
            'Authorization': `Bearer ${user.sub}`,
            'X-User-Role': user.role
          }
        };
      }
    });

    this.auth = auth;
  }

  private convertToAuthRequest(url: URL, init: RequestInit): AuthRequest {
    return {
      url: url.toString(),
      method: init.method || 'GET',
      headers: (init.headers as Record<string, string>) || {},
      query: Object.fromEntries(url.searchParams),
      cookies: {}, // Extract from headers if needed
      body: init.body
    };
  }
}

// Usage
const authenticatedClient = new AuthenticatedLangGraphClient({
  apiUrl: "https://api.langgraph.example.com",
  apiKey: "your-api-key",
  auth: auth
});

The Auth system provides comprehensive authentication and authorization capabilities with flexible event handling, robust error management, and seamless integration with LangGraph applications, ensuring secure and auditable access control.

Install with Tessl CLI

npx tessl i tessl/npm-langchain--langgraph-sdk

docs

assistants.md

auth.md

client.md

crons.md

index.md

react.md

runs.md

store.md

threads.md

tile.json