Skip to content

Bridge Integration Guide

Learn how to integrate Flowless with your custom backend (Flowfull) using the Bridge API for secure session validation.


Overview

The Bridge API is the secure communication layer between your backend (Flowfull) and Flowless. It allows your backend to validate user sessions and receive trust tokens containing user information.


Prerequisites

Before integrating the Bridge API:

  1. ✅ Created a Flowless instance
  2. ✅ Have your Bridge Secret from the Pubflow dashboard
  3. ✅ Have a backend application (Flowfull)
  4. ✅ Understand basic authentication concepts

Step 1: Configure Environment Variables

Add these to your backend's .env file:

bash
# Flowless instance URL
FLOWLESS_URL=https://your-instance-name.pubflow.com

# Bridge Secret (KEEP SECURE!)
BRIDGE_SECRET=bridge_secret_abc123xyz789

# Validation mode (STANDARD, ADVANCED, STRICT)
VALIDATION_MODE=STANDARD

# Cache TTL for trust tokens (seconds)
TRUST_TOKEN_CACHE_TTL=300

DANGER

Never commit your Bridge Secret to version control!

  • Store it in environment variables
  • Use different secrets for dev/staging/prod
  • Rotate it regularly

Step 2: Create Bridge Validation Function

TypeScript/Node.js Example

typescript
import { LRUCache } from 'lru-cache';

// Cache for trust tokens (5 minutes TTL)
const trustTokenCache = new LRUCache<string, string>({
  max: 10000,
  ttl: 1000 * 60 * 5, // 5 minutes
});

interface ValidateSessionOptions {
  sessionId: string;
  ipAddress: string;
  userAgent?: string;
  deviceId?: string;
}

interface UserData {
  id: string;
  email: string;
  name: string;
  last_name?: string;
  username?: string;
  is_verified: boolean;
}

async function validateSession(options: ValidateSessionOptions): Promise<UserData> {
  const { sessionId, ipAddress, userAgent, deviceId } = options;

  // Check cache first
  const cachedToken = trustTokenCache.get(sessionId);
  if (cachedToken) {
    // Validate and decode cached token
    const userData = await validateTrustToken(cachedToken);
    if (userData) {
      return userData;
    }
  }

  // Call Flowless Bridge API
  const response = await fetch(`${process.env.FLOWLESS_URL}/bridge/validate`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Bridge-Secret': process.env.BRIDGE_SECRET!,
    },
    body: JSON.stringify({
      session_id: sessionId,
      ip_address: ipAddress,
      user_agent: userAgent,
      device_id: deviceId,
    }),
  });

  if (!response.ok) {
    throw new Error('Session validation failed');
  }

  const data = await response.json();

  if (!data.success || !data.data.valid) {
    throw new Error('Invalid session');
  }

  // Cache the trust token
  trustTokenCache.set(sessionId, data.data.trust_token);

  return data.data.user;
}

async function validateTrustToken(token: string): Promise<UserData | null> {
  // Implement PASETO token validation here
  // For now, we'll rely on the cache TTL
  // In production, validate the token signature
  try {
    // Decode and validate PASETO token
    // Return user data if valid
    return null; // Placeholder
  } catch (error) {
    return null;
  }
}

Step 3: Create Authentication Middleware

Express.js Example

typescript
import { Request, Response, NextFunction } from 'express';

// Extend Express Request type
declare global {
  namespace Express {
    interface Request {
      user?: UserData;
      sessionId?: string;
    }
  }
}

async function authMiddleware(req: Request, res: Response, next: NextFunction) {
  try {
    // Get session ID from header
    const sessionId = req.headers['x-session-id'] as string;

    if (!sessionId) {
      return res.status(401).json({
        success: false,
        error: 'No session ID provided',
      });
    }

    // Get client info
    const ipAddress = (req.headers['x-forwarded-for'] as string) || req.ip || '';
    const userAgent = req.headers['user-agent'];
    const deviceId = req.headers['x-device-id'] as string;

    // Validate session with Flowless
    const user = await validateSession({
      sessionId,
      ipAddress,
      userAgent,
      deviceId,
    });

    // Attach user to request
    req.user = user;
    req.sessionId = sessionId;

    next();
  } catch (error) {
    return res.status(401).json({
      success: false,
      error: 'Invalid or expired session',
    });
  }
}

export { authMiddleware };

Step 4: Use Middleware in Routes

typescript
import express from 'express';
import { authMiddleware } from './middleware/auth';

const app = express();

// Public routes (no auth required)
app.get('/api/public/health', (req, res) => {
  res.json({ success: true, status: 'healthy' });
});

// Protected routes (auth required)
app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({
    success: true,
    data: req.user,
  });
});

app.get('/api/items', authMiddleware, async (req, res) => {
  // req.user is available here
  const items = await getItemsForUser(req.user!.id);
  
  res.json({
    success: true,
    data: items,
  });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Step 5: Handle Frontend Requests

React Example

typescript
// api.ts
const API_URL = import.meta.env.VITE_API_URL;
const FLOWLESS_URL = import.meta.env.VITE_FLOWLESS_URL;

// Login function
export async function login(email: string, password: string) {
  const response = await fetch(`${FLOWLESS_URL}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password }),
  });

  const data = await response.json();
  
  if (data.success) {
    // Store session ID
    localStorage.setItem('session_id', data.data.session.session_id);
    return data.data.user;
  }
  
  throw new Error(data.error);
}

// API call with session
export async function getProfile() {
  const sessionId = localStorage.getItem('session_id');
  
  if (!sessionId) {
    throw new Error('Not authenticated');
  }

  const response = await fetch(`${API_URL}/api/profile`, {
    headers: {
      'X-Session-ID': sessionId,
    },
  });

  const data = await response.json();
  
  if (!data.success) {
    throw new Error(data.error);
  }
  
  return data.data;
}

Validation Modes

Choose the validation mode based on your security requirements:

bash
VALIDATION_MODE=STANDARD
  • ✅ Session ID validation
  • ✅ IP address validation
  • ⚡ Fast and secure for most applications

ADVANCED

bash
VALIDATION_MODE=ADVANCED
  • ✅ Session ID validation
  • ✅ IP address validation
  • ✅ Device ID validation
  • 🔒 Better security for mobile apps

STRICT

bash
VALIDATION_MODE=STRICT
  • ✅ Session ID validation
  • ✅ IP address validation
  • ✅ Device ID validation
  • ✅ User agent validation
  • 🔐 Maximum security for sensitive applications

Caching Strategy

Why Cache Trust Tokens?

  • Performance: Avoid calling Flowless for every request
  • Scalability: Reduce load on Flowless
  • Cost: Reduce API usage

Cache Implementation

typescript
import { LRUCache } from 'lru-cache';

const cache = new LRUCache<string, string>({
  max: 10000,        // Max 10,000 tokens
  ttl: 1000 * 60 * 5, // 5 minutes
});

// Cache hit rate tracking
let cacheHits = 0;
let cacheMisses = 0;

function getCacheHitRate() {
  const total = cacheHits + cacheMisses;
  return total > 0 ? (cacheHits / total) * 100 : 0;
}

Error Handling

typescript
try {
  const user = await validateSession({ sessionId, ipAddress });
} catch (error) {
  if (error.message === 'Invalid session') {
    // Session expired or invalid
    return res.status(401).json({ error: 'Please login again' });
  }
  
  if (error.message === 'IP address mismatch') {
    // Possible session hijacking
    return res.status(403).json({ error: 'Security violation detected' });
  }
  
  // Other errors
  return res.status(500).json({ error: 'Internal server error' });
}

Next Steps