A flexible, tenant-aware RBAC system designed to be plug-and-play for any project.
- 🔌 Plug-and-play - Import and configure in minutes
- 💻 Developer-defined resources - Define resources/actions in code
- 👨💼 Admin-controlled rules - Super admins create permission rules via UI
- ⚡ Blazing fast - Redis caching for instant permission checks
- 🔧 Framework agnostic - Works with Express, Hono, Fastify, Next.js, etc.
- 🏢 Multi-tenant - Built-in tenant support for SaaS applications
- 🎯 Type-safe - Full TypeScript support
| Package | Description |
|---|---|
@flexauth/core |
Core SDK with permission engine, cache adapters, and conditions |
@flexauth/drizzle |
Drizzle ORM schema and migrations |
@flexauth/express |
Express.js middleware and route guards |
@flexauth/hono |
Hono.js middleware and route guards |
@flexauth/react |
React provider, hooks, and components |
# Using bun
bun add @flexauth/core @flexauth/drizzle
# For Express.js
bun add @flexauth/express
# For Hono
bun add @flexauth/hono
# For React
bun add @flexauth/reactimport { FlexAuth, MemoryAdapter } from '@flexauth/core';
import { db } from './db';
// Initialize FlexAuth
const flexAuth = new FlexAuth({
db,
cache: new MemoryAdapter(), // Use RedisAdapter for production
});
// Define resources and actions
flexAuth
.defineResources([
{ key: 'document', displayName: 'Document', category: 'Content' },
{ key: 'case', displayName: 'Case', category: 'Medical' },
])
.defineActions([
{ key: 'create', displayName: 'Create', category: 'crud' },
{ key: 'read', displayName: 'Read', category: 'crud' },
{ key: 'update', displayName: 'Update', category: 'crud' },
{ key: 'delete', displayName: 'Delete', category: 'crud' },
{ key: 'share', displayName: 'Share', category: 'workflow' },
])
.mapResourceActions('document', ['create', 'read', 'update', 'delete', 'share'])
.mapResourceActions('case', ['create', 'read', 'update', 'delete']);
// Sync to database
await flexAuth.sync();// Simple check
const canEdit = await flexAuth.can(userId, 'update', 'document');
// With context
const canShare = await flexAuth.can(userId, 'share', 'document', {
tenantId: 'hospital_123',
resourceId: 'doc_456',
});
// Detailed check with reason
const result = await flexAuth.check(userId, 'delete', 'case');
console.log(result.allowed, result.reason, result.resolvedBy);
// Bulk check (optimized for UI)
const permissions = await flexAuth.checkBulk(userId, [
{ action: 'read', resource: 'document' },
{ action: 'update', resource: 'document' },
{ action: 'delete', resource: 'document' },
]);
// { 'document:read': true, 'document:update': true, 'document:delete': false }import express from 'express';
import { flexAuthMiddleware, requirePermission } from '@flexauth/express';
const app = express();
// Apply middleware
app.use(flexAuthMiddleware({
flexAuth,
getSubject: (req) => req.user ? { id: req.user.id } : null,
getTenantId: (req) => req.user?.tenantId,
}));
// Protect routes
app.get('/documents/:id',
requirePermission('read', 'document'),
async (req, res) => {
// User has permission
const doc = await getDocument(req.params.id);
res.json(doc);
}
);
// Inline checks
app.get('/cases/:id', async (req, res) => {
const canRead = await req.flexAuth.can('read', 'case');
if (!canRead) {
return res.status(403).json({ error: 'Access denied' });
}
// ...
});import { Hono } from 'hono';
import { flexAuthMiddleware, requirePermission } from '@flexauth/hono';
const app = new Hono();
// Apply middleware
app.use('*', flexAuthMiddleware({
flexAuth,
getSubject: (c) => {
const user = c.get('user'); // Assuming user is attached by auth middleware
return user ? { id: user.id, type: 'user' } : null;
},
getTenantId: (c) => c.req.header('x-tenant-id') || null,
}));
// Protect routes
app.get('/documents/:id',
requirePermission('read', 'document'),
async (c) => {
// User has permission
const id = c.req.param('id');
const doc = await getDocument(id);
return c.json(doc);
}
);
// Inline checks (fully typed c.var.flexAuth)
app.get('/cases/:id', async (c) => {
const canRead = await c.var.flexAuth.can('read', 'case');
if (!canRead) {
return c.json({ error: 'Access denied' }, 403);
}
// ...
});import { FlexAuthProvider, Can, useCan, ProtectedRoute } from '@flexauth/react';
// Wrap your app
function App() {
return (
<FlexAuthProvider permissionsEndpoint="/api/permissions/me">
<Router>
<Routes>
<Route path="/dashboard" element={
<ProtectedRoute action="access" resource="dashboard" redirectTo="/login">
<Dashboard />
</ProtectedRoute>
} />
</Routes>
</Router>
</FlexAuthProvider>
);
}
// Use in components
function DocumentActions({ documentId }) {
const canEdit = useCan('update', 'document');
const canDelete = useCan('delete', 'document');
return (
<div>
<Can action="update" resource="document">
<EditButton id={documentId} />
</Can>
<Can action="delete" resource="document" fallback={<DisabledButton />}>
<DeleteButton id={documentId} />
</Can>
</div>
);
}FlexAuth includes several built-in conditions for common use cases:
import {
approvalRequiredCondition,
ownRecordsCondition,
sameDepartmentCondition,
businessHoursCondition,
ipWhitelistCondition,
} from '@flexauth/core';
// Register built-in conditions
flexAuth.defineConditions([
approvalRequiredCondition,
ownRecordsCondition,
sameDepartmentCondition,
businessHoursCondition,
ipWhitelistCondition,
]);
// Or define custom conditions
flexAuth.defineCondition({
key: 'has_subscription',
displayName: 'Requires Active Subscription',
handler: async (context) => {
const { subjectId } = context;
const hasSubscription = await checkSubscription(subjectId);
return {
passed: hasSubscription,
reason: hasSubscription ? undefined : 'Active subscription required',
};
},
});// Create roles
await flexAuth.admin.createRole(tenantId, {
key: 'doctor',
displayName: 'Doctor',
description: 'Medical doctor role',
});
// Set role permissions
await flexAuth.admin.setRolePermission(roleId, 'case', 'update', {
isAllowed: true,
conditions: [{ conditionKey: 'same_department' }],
});
// Assign roles to users
await flexAuth.admin.assignRole(userId, roleId, tenantId, {
assignedBy: adminId,
});
// Direct user permission overrides
await flexAuth.admin.setSubjectPermission(userId, tenantId, 'case', 'delete', {
isAllowed: true,
reason: 'Special access for project lead',
grantedBy: adminId,
});import { MemoryAdapter, RedisAdapter } from '@flexauth/core';
import Redis from 'ioredis';
// Memory adapter (development/testing)
const cache = new MemoryAdapter();
// Redis adapter (production)
const redis = new Redis(process.env.REDIS_URL);
const cache = new RedisAdapter({
client: redis,
keyPrefix: 'flexauth:',
defaultTTL: 300,
});
// Initialize with cache
const flexAuth = new FlexAuth({
db,
cache,
cacheEnabled: true,
cacheTTL: 300, // 5 minutes
});import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from '@flexauth/drizzle/schema';
const db = drizzle(pool, { schema });
// Run migrations
// Use drizzle-kit to generate and run migrationsflexauth/
├── packages/
│ ├── core/ # Core SDK
│ │ ├── src/
│ │ │ ├── types/ # Type definitions
│ │ │ ├── adapters/ # Cache adapters
│ │ │ ├── conditions/ # Built-in conditions
│ │ │ ├── engine/ # Permission engine
│ │ │ ├── admin/ # Admin services
│ │ │ ├── utils/ # Utilities and errors
│ │ │ ├── flexauth.ts # Main class
│ │ │ └── index.ts # Exports
│ │ └── package.json
│ │
│ ├── drizzle/ # Drizzle ORM integration
│ │ ├── src/
│ │ │ └── schema/ # Database schema
│ │ └── package.json
│ │
│ ├── express/ # Express middleware
│ │ ├── src/
│ │ │ ├── middleware.ts
│ │ │ ├── require-permission.ts
│ │ │ └── require-super-admin.ts
│ │ └── package.json
│ │
│ ├── hono/ # Hono middleware
│ │ ├── src/
│ │ │ ├── middleware.ts
│ │ │ ├── require-permission.ts
│ │ │ └── require-super-admin.ts
│ │ └── package.json
│ │
│ └── react/ # React integration
│ ├── src/
│ │ ├── hooks/ # React hooks
│ │ ├── components/ # UI components
│ │ └── provider.tsx # Context provider
│ └── package.json
│
├── package.json # Root package.json
└── tsconfig.json # Root TypeScript config
# Install dependencies
bun install
# Build all packages
bun run build
# Run type checking
bun x tsc -b
# Run tests
bun testMIT