TypeScript/JavaScript SDK for interacting with the LangGraph REST API server
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.
The Auth system supports:
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;
}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);
}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>;
}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;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 };
}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
};
}// 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
});
});// 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));
}// 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
};
}// 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);
}
}// 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