🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
🕵️ Sicherheitslücken0patch liefert drei Jahre Support für Microsoft Office 2021 - BornCity(07.09.2026 um 00:15 Uhr)
🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
🕵️ Sicherheitslücken0patch liefert drei Jahre Support für Microsoft Office 2021 - BornCity(07.09.2026 um 00:15 Uhr)

🔧 Programmierung 🕛 kürzlich 6 Min Lesezeit
0

Building Dynamic RBAC in React 19: From Permission Strings to Component-Level Access Control

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Building Dynamic RBAC in React 19: From Permission Strings to Component-Level Access Control



String-based permission checks scattered across your React codebase are a maintenance nightmare. I know because I shipped CitizenApp with that anti-pattern, and it nearly bit me when we added our fifth AI feature.



The problem? Permissions were hardcoded in components. When the marketing team wanted to trial a feature with select customers, I had to grep through half the codebase, find every if (user.role === 'admin') check, and create some Frankenstein conditional. Worse, there was no single source of truth for what "feature_x_access" actually meant across our tenant hierarchy.



This post shows you how to build a type-safe, composable RBAC layer that lives outside your components. Your UI asks "can I do X?" and the permission engine answers. Clean separation. Testable. Scales.






The Core Philosophy: Permissions Are Data, Not Logic



Don't embed permissions in your component tree. Treat permissions as configuration that your components consume. This single shift unlocks everything.



Here's what bad looks like:




CODE
// ❌ Don't do this
export function AIFeatureCard() {
const { user } = useAuth();

if (user.role !== 'admin' && user.role !== 'premium_subscriber') {
return null;
}

return <div>AI Feature</div>;
}






Problems:




  1. Role names are magic strings

  2. Permission logic is fragmented across 20 components

  3. Adding a new role? Find and update every check

  4. No way to test permissions without rendering components

  5. No audit trail of what permission was checked where



Here's what good looks like:




CODE
// ✅ Do this
const canAccessAIFeature = await checkPermission('features:ai:access', {
userId,
tenantId,
});

if (canAccessAIFeature) {
return <AIFeatureCard />;
}






Now permissions are data. You can log them, cache them, test them, audit them.






Type-Safe Permission Definitions



Start with TypeScript. Define every permission your app has as a const object:




CODE
// permissions.ts
export const PERMISSIONS = {
// Organization management
'org:create': 'Create organization',
'org:update': 'Update organization settings',
'org:delete': 'Delete organization',
'org:invite_members': 'Invite team members',

// AI features (your feature gates)
'features:ai:access': 'Access any AI feature',
'features:ai:documents': 'Use document analysis',
'features:ai:workflows': 'Create automation workflows',
'features:ai:exports': 'Export AI-generated content',

// Admin
'admin:billing': 'Manage billing',
'admin:audit_logs': 'View audit logs',
} as const;

// Extract type: 'org:create' | 'org:update' | ...
export type PermissionKey = keyof typeof PERMISSIONS;






This gives you autocomplete and catches typos at compile time. No more permission strings as magic text.






Building the Permission Engine



Your permission engine lives on the backend and is queried from React. Here's the FastAPI service:




CODE
# permissions.py
from enum import Enum
from typing import Set
from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session

class RoleType(str, Enum):
OWNER = "owner"
ADMIN = "admin"
MEMBER = "member"
GUEST = "guest"

# Define role -> permissions mapping
ROLE_PERMISSIONS: dict[RoleType, Set[str]] = {
RoleType.OWNER: {
"org:create", "org:update", "org:delete", "org:invite_members",
"features:ai:access", "features:ai:documents", "features:ai:workflows",
"features:ai:exports", "admin:billing", "admin:audit_logs",
},
RoleType.ADMIN: {
"org:update", "org:invite_members",
"features:ai:access", "features:ai:documents", "features:ai:workflows",
"features:ai:exports", "admin:audit_logs",
},
RoleType.MEMBER: {
"features:ai:access", "features:ai:documents", "features:ai:workflows",
},
RoleType.GUEST: {
"features:ai:documents", # Read-only
},
}

# Handle permission inheritance for multi-tenant
class PermissionResolver:
def __init__(self, db: Session):
self.db = db

async def get_user_permissions(
self, user_id: str, tenant_id: str
) -> Set[str]:
"""Resolve all permissions for a user in a tenant."""
# Query user role in this tenant
membership = self.db.query(TenantMembership).filter(
TenantMembership.user_id == user_id,
TenantMembership.tenant_id == tenant_id,
).first()

if not membership:
return set()

# Start with their direct role permissions
permissions = ROLE_PERMISSIONS.get(membership.role, set()).copy()

# Add custom permissions (if you've granted individual perms)
custom = self.db.query(UserPermission).filter(
UserPermission.user_id == user_id,
UserPermission.tenant_id == tenant_id,
).all()

for perm in custom:
permissions.add(perm.permission_key)

return permissions

async def can_perform(
self, user_id: str, tenant_id: str, permission: str
) -> bool:
"""Check if user can perform an action."""
permissions = await self.get_user_permissions(user_id, tenant_id)
return permission in permissions

# Expose as endpoint
@app.post("/api/permissions/check")
async def check_permission(
request: CheckPermissionRequest, # {permission, tenant_id}
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db),
) -> dict[str, bool]:
resolver = PermissionResolver(db)
allowed = await resolver.can_perform(
user_id, request.tenant_id, request.permission
)
return {"allowed": allowed}






Notice: No logic in components. All rules live in ROLE_PERMISSIONS and the database. When you add a feature, you update ROLE_PERMISSIONS once. Done.






React Hook: usePermission



Now the React side. Create a hook that queries your permission endpoint:




CODE
// usePermission.ts
import { useAuth } from './useAuth';
import { PermissionKey, PERMISSIONS } from './permissions';

interface UsePermissionOptions {
cacheSeconds?: number;
}

export function usePermission(
permissionKey: PermissionKey,
options: UsePermissionOptions = {}
) {
const { user, tenantId } = useAuth();
const [allowed, setAllowed] = React.useState<boolean | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<Error | null>(null);

React.useEffect(() => {
if (!user || !tenantId) {
setLoading(false);
setAllowed(false);
return;
}

const checkPerm = async () => {
try {
const response = await fetch('/api/permissions/check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
permission: permissionKey,
tenant_id: tenantId,
}),
});

if (!response.ok) throw new Error('Permission check failed');
const data = await response.json();
setAllowed(data.allowed);
} catch (err) {
setError(err as Error);
setAllowed(false); // Fail closed
} finally {
setLoading(false);
}
};

checkPerm();
}, [user?.id, tenantId, permissionKey]);

return { allowed, loading, error };
}






Usage in components:




CODE
// AIFeatureCard.tsx
export function AIFeatureCard() {
const { allowed, loading } = usePermission('features:ai:access');

if (loading) return <Skeleton />;
if (!allowed) return null;

return <div className="p-4 bg-blue-50">AI Feature</div>;
}






Clean. Testable. Type-safe.






Caching: The Performance Multiplier



This burned me: I shipped this without caching, and every component requesting the same permission hammered the backend. Use React Query:




CODE
// usePermission.ts (improved)
import { useQuery } from '@tanstack/react-query';

export function usePermission(
permissionKey: PermissionKey,
options: UsePermissionOptions = {}
) {
const { user, tenantId } = useAuth();
const cacheSeconds = options.cacheSeconds ?? 300; // 5 min default

return useQuery({
queryKey: ['permission', tenantId, permissionKey],
queryFn: async () => {
const response = await fetch('/api/permissions/check', {
method: 'POST',
body: JSON.stringify({
permission: permissionKey,
tenant_id: tenantId,
}),
});
const data = await response.json();
return data.allowed;
},
staleTime: cacheSeconds * 1000,
enabled: !!user && !!tenantId,
});
}






Now the second component asking for features:ai:access hits the cache, not your backend.






Gotcha: Stale Permission State After Tenant Changes

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 39%
🟡 In Evaluierung 33%
🟢 Keine Auswirkung 18%
Spannende Innovation 10%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
ChatGPT showing blank screen [Fix]
1 Quelle
Excel keeps people on Windows, and a Linux distro creator wants Microsoft to end that
1 Quelle
Sofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building Dynamic RBAC in React 19: From Permission Strings to Component-Level Access Control

Thematisch verwandte Begriffe: Building, Dynamic, RBAC, React · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...