A Next.js application demonstrating Attribute-Based Access Control (ABAC), Relationship-Based Access Control (ReBAC), and AI-powered chat with Fine-Grained Authorization (FGA) using Permit.io. This demo simulates LEGO's internal product catalog and collaborative document management, where access is controlled through user/resource attributes, hierarchical relationships, and an AI chat agent that respects the same permissions.
The demo includes a LangChain-based AI chat agent that demonstrates how Fine-Grained Authorization (FGA) works with RAG (Retrieval-Augmented Generation):
- Permission-Aware Retrieval: Uses FAISS vector stores with Permit.io integration to filter documents/products before retrieval
- Runtime Permission Checks: Uses
LangchainPermissionsCheckToolto check access levels (full, masked, metadata-only) for each retrieved item - Automatic Data Masking: Applies masking based on permission checks before sending context to the LLM
- Secure by Default: The LLM only sees information the user is authorized to access, with sensitive data already masked
The chat agent is accessible from both the Products and Documents pages, and respects the same ABAC and ReBAC policies as the rest of the application.
- Product Catalog - Browse LEGO products with dynamic permissions based on user attributes
- Masking/Redaction - Confidential information is redacted based on user attributes
- Grid/List View - Toggle between different product display modes
- Document Library - Collaborative documents with relationship-based permissions
- Hierarchical Access - Document access derived from product relationships
- Role Derivation - Product leads automatically get editor access to documents
- Document Sharing - Product leads can share documents with other users
- Content Masking - Metadata-only viewers see only document names, content is hidden
- Accordion View - Expandable document rows showing full content when opened
- Intelligent RAG - LangChain-based AI chat agent that answers questions about products and documents
- Permission-Aware Retrieval - Uses Permit.io to filter and mask information based on user permissions
- Fine-Grained Data Masking - Automatically redacts sensitive information based on access levels
- Context-Aware Responses - LLM only sees content the user is authorized to access
- Multi-Resource Support - Chat about both products and documents with unified access control
- Demo Personas - Switch between different user roles to see access control in action
- Policy Engine - Fine-grained access control using Permit.io
- OIDC Authentication - Secure authentication using NextAuth.js
This demo implements a realistic LEGO product access control scenario:
- All employees can view released products (public catalog)
- Theme designers can view pre-release + public products for their assigned themes
- Theme designers can view pre-release + confidential products for their themes, but with masking (redacted pricing/details)
- LEGO workstation users get full unmasked access to confidential products for their themes (secure environment)
- Theme: City, Space, Castle, Ninjago, Friends, Technic
- Status:
releasedorpre-release - Confidentiality:
publicorconfidential - Release Date: ISO date string
- Organization: Array of theme names the user has access to (e.g.,
["City", "Space"]) - Workstation:
office_workstationorlego_workstation(secure facility) - Role: employee, designer, manager, team_lead, etc.
Attributes: organization: [], workstation: office_workstation, role: employee
Can see (4 products):
- All released products across all themes
- NO pre-release products (no theme access)
Example: Regular office employee browsing the public LEGO catalog.
Attributes: organization: ["City"], workstation: office_workstation, role: designer
Can see (6 products):
- All 4 released products
- City Fire Brigade (pre-release, public, City) - FULL ACCESS
- City Mega Construction (pre-release, confidential, City) - MASKED
⚠️
Why masked?: Working from office workstation, not secure LEGO facility.
Attributes: organization: ["Space"], workstation: lego_workstation, role: designer
Can see (7 products):
- All 4 released products
- Space Colony Base (pre-release, public, Space) - FULL ACCESS
- Space Intergalactic Cruiser (pre-release, confidential, Space) - FULL UNMASKED ACCESS ✅
Why unmasked?: At secure LEGO workstation, can see all confidential details.
Attributes: organization: ["City", "Space", "Castle"], workstation: office_workstation, role: senior_designer
Can see (10 products):
- All 4 released products
- City Fire Brigade (pre-release, public, City) - full
- Space Colony Base (pre-release, public, Space) - full
- Dragon Mountain Cave (pre-release, public, Castle) - full
- City Mega Construction (pre-release, confidential, City) - MASKED
⚠️ - Space Intergalactic Cruiser (pre-release, confidential, Space) - MASKED
⚠️ - Castle Kingdom Palace (pre-release, confidential, Castle) - MASKED
⚠️
Why masked?: Has access to 3 themes but working from office, so confidential products are redacted.
Attributes: organization: ["Ninjago"], workstation: lego_workstation, role: team_lead
Can see (5 products):
- All 4 released products
- Ninjago Ultimate Showdown (pre-release, confidential, Ninjago) - FULL UNMASKED ACCESS ✅
Why unmasked?: Team lead at LEGO workstation gets full access to confidential Ninjago products.
Attributes: organization: ["Friends"], workstation: office_workstation, role: manager
Can see (5 products):
- All 4 released products
- Friends Heartlake Resort (pre-release, confidential, Friends) - MASKED
⚠️
Why masked?: Working from office, so sensitive pricing and details are redacted.
This demo implements Relationship-Based Access Control (ReBAC) for collaborative document management. Documents belong to products, and access is derived through hierarchical relationships.
- Product leads automatically get editor access to all documents belonging to their products
- Product stakeholders automatically get viewer access (metadata only - name visible, content masked) to documents belonging to their products
- Direct role assignments - Product leads can share documents by granting editor role directly to users
- Document permissions:
- Editor: Can view full content, edit, and share documents
- Viewer: Can only see document name (content is masked)
- Moderator: Can view, edit, and share documents (same as editor currently)
- Document Types:
design,financial,marketing,spec - Parent-Child Relationship: Each document belongs to a product via
parentrelationship - Role Derivation:
product:lead→document:editor(via parent relation)product:stakeholder→document:viewer(via parent relation)
Product Lead (e.g., City Designer as lead of City Police Station):
- Can view the product
- Automatically gets
editorrole on all documents belonging to the product - Can view full document content
- Can edit documents
- Can share documents with other users (granting them editor role)
Product Stakeholder (e.g., Senior Designer as stakeholder):
- Cannot view the product directly
- Automatically gets
viewerrole on all documents belonging to the product - Can only see document names (content is masked/hidden)
- Cannot edit or share
Direct Editor (user granted editor role via sharing):
- Can view full document content
- Can edit the document
- Cannot share (only product leads can share)
- Node.js 18+ installed
- Docker (for running Permit.io PDP locally)
- Permit.io account (free at permit.io)
npm install- Create a free account at permit.io
- Create a new project
- Copy your API key
Copy .env.example to .env.local:
cp .env.example .env.localEdit .env.local with your configuration:
# NextAuth
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key-here
# OIDC Provider
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_ISSUER=https://your-oidc-provider.com
# Permit.io
PERMIT_API_KEY=your-permit-api-key
PERMIT_PDP_URL=http://localhost:7766Generate NEXTAUTH_SECRET:
openssl rand -base64 32Start the local PDP container:
docker run -p 7766:7000 --env PDP_API_KEY=<YOUR_PERMIT_API_KEY> permitio/pdp-v2:latestRun the setup script to configure ABAC policies, condition sets, and demo users:
npm run setup:permitThis script creates:
- Product resource type with attributes (theme, confidentiality, status, releaseDate)
- User attributes (organization, workstation, role)
- 6 condition sets (3 resource sets + 3 user sets)
- 4 condition set rules implementing the ABAC policies
- 2 roles (employee, designer)
- 6 demo personas synced with attributes
- 15 product instances synced with attributes
The AI chat agent requires a Python service. You can run it via Docker Compose (recommended for development):
# Start all services (PDP, Next.js app, Python chat agent)
docker-compose -f docker-compose.dev.yml up --buildOr run the Python service separately:
- Install Python dependencies:
cd services/chat-agent
pip install -r requirements.txt- Export data from TypeScript to JSON:
npm run export:data- Set environment variables in
services/chat-agent/.env:
PERMIT_API_KEY=your_permit_api_key
PERMIT_PDP_URL=http://localhost:7766
OPENAI_API_KEY=sk-...- Run the FastAPI server:
cd services/chat-agent
uvicorn api_server:app --reload --port 8000npm run devOpen http://localhost:3000 in your browser.
.
├── app/
│ ├── api/
│ │ ├── auth/[...nextauth]/ # NextAuth API routes
│ │ ├── products/ # Product catalog API with ABAC
│ │ ├── documents/ # Document library API with ReBAC
│ │ ├── chat/ # Chat agent API proxy
│ │ ├── users/ # User listing for document sharing
│ │ └── demo/ # Demo persona switching
│ ├── products/ # Product catalog page (ABAC demo)
│ ├── documents/ # Document library page (ReBAC demo)
│ ├── chat/ # Dedicated chat page
│ ├── dashboard/ # Protected dashboard
│ └── page.tsx # Home page
├── components/
│ ├── products/ # Product card components
│ ├── documents/ # Document components (PermissionDialog, ShareDialog)
│ ├── chat/ # Chat dialog component
│ └── demo/ # Persona switcher
├── services/
│ └── chat-agent/ # Python LangChain chat agent service
│ ├── api_server.py # FastAPI server
│ ├── chat_service.py # Chat agent logic
│ ├── permit_masked_retriever.py # Permission-aware retriever wrapper
│ ├── data_loader.py # Data loading utilities
│ └── data/ # JSON data files (products, documents)
├── lib/
│ ├── data/
│ │ ├── products.ts # Product data (15 products)
│ │ ├── documents.ts # Document data (14 documents)
│ │ └── personas.ts # Demo personas (6 users)
│ ├── permit.ts # Permit.io SDK setup
│ └── permit-helpers.ts # ABAC and ReBAC helper functions
├── scripts/
│ ├── setup-permit.ts # Permit.io configuration script
│ └── export-data.ts # Export TS data to JSON for Python service
├── docker-compose.dev.yml # Docker Compose for dev environment
└── .env.local # Environment variables
When a user logs in or switches persona, their attributes are synced:
await permit.api.syncUser({
key: "persona-city-office",
email: "[email protected]",
attributes: {
organization: ["City"],
workstation: "office_workstation",
role: "designer"
}
});Products are synced with their attributes:
await permit.api.resourceInstances.create({
resource: "product",
key: "prod-008",
attributes: {
theme: "City",
status: "pre-release",
confidentiality: "confidential",
releaseDate: "2025-09-01"
}
});Resource Sets match products by attributes:
{
key: "released_products",
type: "resourceset",
resource_id: "product",
conditions: {
allOf: [{ "resource.status": { equals: "released" } }]
}
}User Sets match users by attributes:
{
key: "theme_access_users",
type: "userset",
conditions: {
allOf: [
{
"user.organization": {
"array_contains": { ref: "resource.theme" }
}
}
]
}
}{
user_set: "theme_access_users",
resource_set: "prerelease_confidential",
permission: "product:view"
}This rule means: "Users whose organization contains the product's theme can view pre-release confidential products (but masked)"
The app queries the PDP to check all permissions in a single batch call:
const response = await fetch(`${pdpUrl}/user-permissions`, {
method: "POST",
body: JSON.stringify({
user: {
key: userId,
attributes: { organization, workstation }
},
resources: ["prod-001", "prod-002", ...],
context: { enable_abac_user_permissions: true }
})
});Based on permissions returned, the UI either shows full details or applies masking:
if (canViewFull) {
accessLevel = "full";
} else if (canView && product.confidentiality === "confidential") {
accessLevel = "masked";
displayProduct = applyProductMask(product); // Redact sensitive fields
}When a user has view permission but not view_full permission on a confidential product:
- Price is hidden (shows
[REDACTED]) - Description is redacted
- Image is replaced with placeholder
- Product name is still visible
Documents inherit access from their parent products through relationship tuples:
-
Relationship Tuples:
product:prod-001 parent document:doc-001- Defines that
doc-001belongs toprod-001via theparentrelation
- Defines that
-
Role Derivation Rules:
- Users with
product:leadrole on a product automatically getdocument:editorrole on all child documents - Users with
product:stakeholderrole on a product automatically getdocument:viewerrole on all child documents
- Users with
-
Direct Assignments: Product leads can share documents by directly assigning
document:editorrole to users
When a user has only view_metadata permission on a document:
- Document name is visible
- Document content is completely hidden (shows placeholder text)
- Product name is visible
- Document type badge is visible
A secure environment attribute that grants full access to confidential information. This simulates:
- Physical security (only accessible in LEGO facilities)
- Network security (isolated workstations)
- Compliance requirements (audit trails for sensitive data access)
Represents which product lines a user works on. Uses array_contains matching to check if the user's organization array includes the product's theme.
npm run dev- Start development servernpm run build- Build for productionnpm start- Start production servernpm run setup:permit- Configure Permit.io policiesnpm run export:data- Export TypeScript data to JSON for Python chat agent
Use the persona switcher in the sidebar to test different access levels without logging out. Each persona demonstrates different ABAC and ReBAC scenarios:
- Products Page: Test ABAC with attribute-based filtering and masking
- Documents Page: Test ReBAC with relationship-based access and role derivation
- Chat Agent: Ask questions about products and documents - the AI respects the same permissions as the UI
Switch between personas to see how both access control models work together, and how the AI chat agent respects the same permissions.
The chat agent is available from both the Products and Documents pages, or visit /chat directly. Try asking:
- "Tell me about City products"
- "What documents are available for Space products?"
- "Which confidential products can I access?"
- "Show me products I can see with full details"
The chat agent will:
- Retrieve relevant products/documents using semantic search
- Filter out items you don't have access to (using Permit.io)
- Check access levels for each retrieved item
- Apply masking to sensitive information
- Generate a response based only on what you're authorized to see
While the current setup process works well, here are some enhancements that could make it even easier for new users:
- Environment variable validation: Check for required env vars on app startup and provide helpful error messages
- Setup verification script: Add
npm run verify:setupto check if Permit.io is properly configured - Docker Compose integration: Auto-start PDP alongside the Next.js dev server
- Health check endpoint: API route to verify PDP connectivity and configuration status
- Pre-commit hooks: Validate that setup has been run before allowing commits
- Setup status indicator: Show in UI if demo is properly configured
- Interactive setup wizard: Guide users through account creation and configuration
- Error recovery: Better error messages with actionable next steps when PDP is unreachable
These improvements would reduce the manual setup steps while maintaining the educational value of understanding each component.
The chat agent demonstrates how to integrate Fine-Grained Authorization with RAG (Retrieval-Augmented Generation):
- Vector Store Setup: Products and documents are embedded using OpenAI embeddings and stored in FAISS vector stores
- Permission-Aware Retrieval: Before retrieval, the system uses Permit.io to filter out items the user doesn't have access to
- Runtime Permission Checks: For each retrieved item,
LangchainPermissionsCheckToolchecks access levels:- Products:
view_full(unmasked) vsview(may be masked if confidential) - Documents:
view(full) vsview_metadata(content masked)
- Products:
- Data Masking: Based on permission checks, sensitive information is redacted before sending to the LLM
- Context Generation: The LLM receives only the information the user is authorized to see, with masking already applied
- Response Generation: The LLM generates responses based on the masked, permission-filtered context
This ensures that the AI never exposes information the user shouldn't have access to, and the responses reflect only what the user can see.
- Permit.io Documentation
- ABAC Overview
- ReBAC Overview - Relationship-Based Access Control
- Condition Sets
- Resource Relations - Hierarchical relationships
- Role Derivations - Automatic role inheritance
- LangChain Documentation
- langchain-permit Package - Permit.io integration for LangChain
- NextAuth.js Documentation
- Next.js Documentation
MIT