Integration Patterns
How you integrate FLXBL depends on your application architecture. This guide covers the most common patterns: Backend-for-Frontend (BFF), direct mobile/SPA access, and serverless functions.
Pattern 1: Backend-for-Frontend (BFF)
Recommended for: Web applications with server-side rendering (Nuxt, Next.js, SvelteKit, Remix)
Your web server acts as a secure proxy between the browser and FLXBL:
- Stores tokens in HTTP-only cookies (not accessible to JavaScript)
- Implements your application's authorization logic
- Can use admin API key for privileged operations
Why BFF?
| Benefit | Description |
|---|---|
| Token security | HTTP-only cookies can't be stolen by XSS attacks |
| Centralized auth | One place for all authentication and authorization logic |
| API key protection | Admin API keys stay on your server, never exposed to browser |
| Token refresh | Server handles refresh automatically without client complexity |
Implementation: Login Endpoint
// server/api/auth/login.post.ts (Nuxt example)
export default defineEventHandler(async (event) => {
const { email, password } = await readBody(event);
// Call FLXBL Identity API
const response = await $fetch(`${FLXBL_URL}/api/v1/identity/${tenantId}/login`, {
method: 'POST',
body: { identifier: email, password }
});
// Store tokens in HTTP-only cookies (not accessible to JavaScript)
setCookie(event, 'access_token', response.accessToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: response.expiresIn
});
setCookie(event, 'refresh_token', response.refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
// Return user info (without tokens)
return { user: response.user };
});Implementation: Auth Middleware
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Skip public routes
if (isPublicRoute(event.path)) return;
const accessToken = getCookie(event, 'access_token');
if (!accessToken) {
throw createError({ statusCode: 401, message: 'Not authenticated' });
}
// Fetch user profile from FLXBL
const user = await $fetch(`${FLXBL_URL}/api/v1/identity/${tenantId}/me`, {
headers: { Authorization: `Bearer ${accessToken}` }
});
// YOUR authorization logic - FLXBL stores roles, you enforce them
const requiredRole = getRequiredRole(event.path);
if (requiredRole && !hasMinimumRole(user.role, requiredRole)) {
throw createError({ statusCode: 403, message: 'Insufficient permissions' });
}
// Attach user to request context
event.context.user = user;
});
// Your role hierarchy
const ROLE_LEVELS = { viewer: 1, editor: 2, admin: 3 };
function hasMinimumRole(userRole, requiredRole) {
return (ROLE_LEVELS[userRole] || 0) >= (ROLE_LEVELS[requiredRole] || 0);
}Implementation: Data Proxy
Proxy data requests to FLXBL, adding the user's token from the cookie:
// server/api/data/[...path].ts
// Proxy data requests to FLXBL with user's token
export default defineEventHandler(async (event) => {
const accessToken = getCookie(event, 'access_token');
const path = event.context.params.path;
// Forward request to FLXBL Dynamic API
const response = await $fetch(`${FLXBL_URL}/api/v1/dynamic/${path}`, {
method: event.method,
headers: { Authorization: `Bearer ${accessToken}` },
body: event.method !== 'GET' ? await readBody(event) : undefined
});
return response;
});Token Refresh Strategy
Handle token expiration gracefully by refreshing when needed:
// Token refresh logic (BFF example)
async function refreshTokens(event) {
const refreshToken = getCookie(event, 'refresh_token');
if (!refreshToken) {
throw createError({ statusCode: 401, message: 'No refresh token' });
}
try {
const response = await $fetch(`${FLXBL_URL}/api/v1/identity/${tenantId}/refresh`, {
method: 'POST',
body: { refreshToken }
});
// Update access token cookie
setCookie(event, 'access_token', response.accessToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: response.expiresIn
});
return response.accessToken;
} catch (error) {
// Refresh token expired - user must log in again
deleteCookie(event, 'access_token');
deleteCookie(event, 'refresh_token');
throw createError({ statusCode: 401, message: 'Session expired' });
}
}Pattern 2: Direct API Access (Mobile/SPA)
Recommended for: Mobile apps, trusted single-page applications
Your app communicates directly with FLXBL:
- Stores tokens in secure storage (Keychain, SecureStore)
- Uses end-user JWT for all operations
- Never exposes admin API keys to client code
Security Considerations
| Do | Don't |
|---|---|
| Use SecureStore/Keychain for tokens | Store tokens in AsyncStorage/localStorage |
| Implement token refresh logic | Assume tokens never expire |
| Use HTTPS for all requests | Hardcode API keys in client code |
| Clear tokens on logout | Leave tokens after user logs out |
Implementation: React Native / Expo
// React Native / Expo example
import * as SecureStore from 'expo-secure-store';
async function login(email: string, password: string) {
const response = await fetch(`${FLXBL_URL}/api/v1/identity/${tenantId}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: email, password })
});
const { accessToken, refreshToken, user } = await response.json();
// Store tokens in secure storage (Keychain on iOS, Keystore on Android)
await SecureStore.setItemAsync('access_token', accessToken);
await SecureStore.setItemAsync('refresh_token', refreshToken);
return user;
}
async function fetchData(endpoint: string) {
const accessToken = await SecureStore.getItemAsync('access_token');
const response = await fetch(`${FLXBL_URL}/api/v1/dynamic/${endpoint}`, {
headers: { Authorization: `Bearer ${accessToken}` }
});
// Handle 401 by refreshing token
if (response.status === 401) {
await refreshAccessToken();
return fetchData(endpoint); // Retry
}
return response.json();
}Authorization in Mobile Apps
Since you can't hide logic in a mobile app, authorization should be:
- Enforced on the server — FLXBL returns only data the user can access
- UI-level only on client — Hide buttons/screens based on role, but don't rely on it for security
// Check role for UI purposes (not security)
function canEditProduct(user) {
return ['editor', 'admin'].includes(user.role);
}
// The real protection is on your backend or in FLXBL scopes
<Button
disabled={!canEditProduct(user)}
onPress={handleEdit}
>
Edit Product
</Button>Pattern 3: Serverless / Edge Functions
Recommended for: Cloudflare Workers, Vercel Edge, AWS Lambda, backend services
Your functions use API keys for all operations:
- No end-user authentication (API key represents your service)
- API keys scoped to specific entities and operations
- Service accounts for audit trails
Implementation
// Cloudflare Worker / Vercel Edge Function
export default async function handler(request: Request) {
// API key from environment variables
const apiKey = process.env.FLXBL_API_KEY;
// Make request to FLXBL with scoped API key
const response = await fetch(`${FLXBL_URL}/api/v1/dynamic/Product`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
return new Response(await response.text(), {
headers: { 'Content-Type': 'application/json' }
});
}Best Practices for Serverless
- Use scoped API keys — One key per function with minimal permissions
- Store keys in environment variables — Never hardcode in source
- Consider service accounts — For better audit trails
- Set key expiration — Rotate keys periodically
Choosing the Right Pattern
| Application Type | Pattern | Why |
|---|---|---|
| Next.js / Nuxt / SvelteKit | BFF | Best security with SSR, HTTP-only cookies |
| React Native / Flutter | Direct | Secure storage available, no server needed |
| SPA (React, Vue) | BFF or Direct | BFF preferred; Direct if no backend available |
| Cloudflare Workers | Serverless | No user auth, just API key access |
| Cron jobs / Webhooks | Serverless | Service account for audit, scoped key |
| Admin dashboard | BFF with admin key | Use API key for privileged operations |
Common Implementation Details
Environment Variables
# .env (never commit this file)
FLXBL_URL=https://api.flxbl.dev
FLXBL_TENANT_ID=your_tenant_id
FLXBL_API_KEY=flxbl_your_api_key # For server-side onlyError Handling
// Handle common FLXBL errors
async function handleFlxblRequest(request) {
const response = await fetch(request);
switch (response.status) {
case 401:
// Token expired or invalid
throw new AuthError('Not authenticated');
case 403:
// Valid token but insufficient permissions
throw new ForbiddenError('Insufficient permissions');
case 404:
// Entity or record not found
throw new NotFoundError('Resource not found');
case 429:
// Rate limited
throw new RateLimitError('Too many requests');
default:
return response.json();
}
}Next Steps
- End-User Authentication — Set up identity entities
- API Keys — Create scoped keys for your backends
- Architecture Guide — Understand the two-plane model
- Access Control — Learn about roles and permissions