Skip to content
On this page

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

Flowless Hybrid Cache ​

Flowless uses a dual-layer hybrid cache:

  1. Ultra/Redis - Distributed cache shared across instances
  2. UnifiedSessionCache - Fast LRU local cache

When you call /auth/bridge/validate:

  • First checks Ultra/Redis (distributed)
  • Falls back to UnifiedCache (local)
  • On cache miss: queries database and repopulates both caches

Cache Invalidation ​

When your backend updates user data directly, you can invalidate the cache:

typescript
// After updating user in your database
await db.updateTable('users')
  .set({ name: 'New Name' })
  .where('id', '=', userId)
  .execute();

// Invalidate Flowless cache to force fresh data
await fetch(`${FLOWLESS_URL}/auth/invalidate/me`, {
  method: 'POST',
  headers: {
    'X-Session-ID': sessionId,
  },
});

// Next bridge/validate call will fetch fresh data from DB

Rate Limit: 10 requests per 5 minutes per user

Your Backend Cache Implementation ​

For optimal performance, implement caching in your backend:

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 ​