Roles
📦 Prompts
✨ The Prompt Phrase
Build a role-based access control system for a web application: define roles as named collections of permissions, where each permission is a combination of a resource type and an allowed action such as invoice:read or invoice:write. Assign roles to users. On each request, resolve the requesting user's permissions from their assigned roles and evaluate whether the requested resource and action are permitted. Support role inheritance so a senior role includes all permissions of a junior role automatically.
💻 Code Preview
📦 All-in-One Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>RBAC – Role-Based Access Control · Interactive Tutorial</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet"/>
<style>
/* ── CSS Variables ─────────────────────────────────────── */
:root {
--bg: #0d0f1a;
--surface: #141728;
--surface-2: #1c2038;
--surface-3: #242847;
--border: #2e3260;
--text: #e2e4f0;
--text-2: #9ca3c4;
--text-3: #5b6290;
--purple: #8b5cf6;
--purple-l: #a78bfa;
--blue: #3b82f6;
--blue-l: #60a5fa;
--green: #10b981;
--green-l: #34d399;
--amber: #f59e0b;
--red: #ef4444;
--pink: #ec4899;
--grad-1: linear-gradient(135deg,#8b5cf6,#3b82f6);
--grad-2: linear-gradient(135deg,#3b82f6,#10b981);
--grad-3: linear-gradient(135deg,#ec4899,#8b5cf6);
--radius: 14px;
--shadow: 0 8px 32px rgba(0,0,0,0.4);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
font-family:'Inter',system-ui,sans-serif;
background:var(--bg);
color:var(--text);
line-height:1.7;
overflow-x:hidden;
}
::selection{background:rgba(139,92,246,0.35);color:#fff}
::-webkit-scrollbar{width:6px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
/* ── Utility ───────────────────────────────────────────── */
.grad-text{
background:var(--grad-1);
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
background-clip:text;
}
.badge{
display:inline-flex;align-items:center;gap:.35rem;
padding:.25rem .75rem;border-radius:20px;
font-size:.72rem;font-weight:700;letter-spacing:.06em;
text-transform:uppercase;
}
.badge-purple{background:rgba(139,92,246,.18);color:var(--purple-l)}
.badge-blue {background:rgba(59,130,246,.18);color:var(--blue-l)}
.badge-green {background:rgba(16,185,129,.18);color:var(--green-l)}
.badge-amber {background:rgba(245,158,11,.18);color:var(--amber)}
.badge-red {background:rgba(239,68,68,.18);color:var(--red)}
.badge-pink {background:rgba(236,72,153,.18);color:var(--pink)}
/* ── Layout ────────────────────────────────────────────── */
.container{max-width:900px;margin:0 auto;padding:0 1.25rem}
/* ── Progress bar ──────────────────────────────────────── */
#progress-bar{
position:fixed;top:0;left:0;height:3px;width:0%;
background:var(--grad-1);z-index:9999;
transition:width .1s linear;
}
/* ── Nav ───────────────────────────────────────────────── */
nav{
position:sticky;top:0;z-index:100;
background:rgba(13,15,26,.85);
backdrop-filter:blur(16px);
border-bottom:1px solid var(--border);
padding:.75rem 1.25rem;
}
.nav-inner{
max-width:900px;margin:0 auto;
display:flex;align-items:center;justify-content:space-between;gap:1rem;
}
.nav-brand{
font-weight:800;font-size:1rem;
background:var(--grad-1);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;white-space:nowrap;
}
.nav-links{
display:flex;gap:.25rem;list-style:none;
overflow-x:auto;scrollbar-width:none;
}
.nav-links::-webkit-scrollbar{display:none}
.nav-links a{
font-size:.72rem;font-weight:600;
color:var(--text-2);text-decoration:none;
padding:.3rem .6rem;border-radius:6px;
transition:background .15s,color .15s;white-space:nowrap;
}
.nav-links a:hover{background:var(--surface-2);color:var(--text)}
/* ── Hero ──────────────────────────────────────────────── */
.hero{
padding:5rem 1.25rem 3.5rem;
text-align:center;
position:relative;overflow:hidden;
}
.hero-glow{
position:absolute;top:-80px;left:50%;transform:translateX(-50%);
width:600px;height:400px;
background:radial-gradient(ellipse at center,rgba(139,92,246,.18) 0%,transparent 70%);
pointer-events:none;
}
.hero-eyebrow{margin-bottom:1rem}
.hero h1{
font-size:clamp(2rem,5vw,3.5rem);
font-weight:900;line-height:1.1;
letter-spacing:-.03em;margin-bottom:1.25rem;
}
.hero-sub{
font-size:1.05rem;color:var(--text-2);
max-width:580px;margin:0 auto 2rem;
}
.hero-stats{
display:flex;flex-wrap:wrap;gap:1rem;
justify-content:center;margin-top:2rem;
}
.hero-stat{
background:var(--surface);border:1px solid var(--border);
border-radius:12px;padding:.75rem 1.25rem;
display:flex;flex-direction:column;align-items:center;gap:.2rem;
}
.hero-stat-num{font-size:1.5rem;font-weight:800}
.hero-stat-lbl{font-size:.72rem;color:var(--text-3);font-weight:600;text-transform:uppercase;letter-spacing:.07em}
/* ── Section ───────────────────────────────────────────── */
section{padding:3rem 0}
.section-header{
display:flex;align-items:center;gap:.75rem;
margin-bottom:1.75rem;
}
.section-icon{
width:42px;height:42px;border-radius:12px;
display:flex;align-items:center;justify-content:center;
font-size:1.2rem;flex-shrink:0;
}
.section-header h2{font-size:1.5rem;font-weight:800;letter-spacing:-.02em}
/* ── Cards ─────────────────────────────────────────────── */
.card{
background:var(--surface);border:1px solid var(--border);
border-radius:var(--radius);padding:1.5rem;
box-shadow:var(--shadow);
}
.card-grid{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
gap:1rem;
}
.feature-card{
background:var(--surface-2);border:1px solid var(--border);
border-radius:var(--radius);padding:1.25rem;
transition:transform .2s,box-shadow .2s,border-color .2s;
}
.feature-card:hover{
transform:translateY(-3px);
box-shadow:0 12px 40px rgba(139,92,246,.15);
border-color:rgba(139,92,246,.4);
}
.feature-card .icon{font-size:1.75rem;margin-bottom:.75rem}
.feature-card h3{font-size:.95rem;font-weight:700;margin-bottom:.4rem}
.feature-card p{font-size:.83rem;color:var(--text-2);line-height:1.6}
/* ── Accordion ─────────────────────────────────────────── */
.accordion{display:flex;flex-direction:column;gap:.6rem}
.acc-item{
background:var(--surface);border:1px solid var(--border);
border-radius:var(--radius);overflow:hidden;
}
.acc-trigger{
width:100%;display:flex;align-items:center;justify-content:space-between;
padding:1rem 1.25rem;background:none;border:none;
color:var(--text);font-family:inherit;font-size:.95rem;font-weight:600;
cursor:pointer;text-align:left;gap:1rem;
transition:background .15s;
}
.acc-trigger:hover{background:var(--surface-2)}
.acc-trigger .step-num{
width:28px;height:28px;border-radius:50%;
background:var(--grad-1);
display:flex;align-items:center;justify-content:center;
font-size:.75rem;font-weight:800;flex-shrink:0;color:#fff;
}
.acc-trigger .chevron{
transition:transform .25s;flex-shrink:0;
color:var(--text-3);font-size:1.1rem;
}
.acc-trigger[aria-expanded="true"] .chevron{transform:rotate(180deg)}
.acc-body{
max-height:0;overflow:hidden;
transition:max-height .35s cubic-bezier(.4,0,.2,1);
}
.acc-body-inner{padding:0 1.25rem 1.25rem}
.acc-body-inner p{font-size:.875rem;color:var(--text-2);margin-bottom:.75rem}
.acc-body-inner p:last-child{margin-bottom:0}
/* ── Code blocks ───────────────────────────────────────── */
.code-wrap{position:relative;margin:1rem 0}
.code-label{
display:flex;align-items:center;justify-content:space-between;
background:var(--surface-3);border:1px solid var(--border);
border-bottom:none;border-radius:var(--radius) var(--radius) 0 0;
padding:.5rem 1rem;
font-size:.72rem;font-weight:700;color:var(--text-3);
text-transform:uppercase;letter-spacing:.07em;
}
.copy-btn{
background:var(--surface-2);border:1px solid var(--border);
color:var(--text-2);font-family:inherit;font-size:.72rem;font-weight:600;
padding:.25rem .65rem;border-radius:6px;cursor:pointer;
transition:background .15s,color .15s;
}
.copy-btn:hover{background:var(--purple);color:#fff;border-color:var(--purple)}
pre{
background:var(--surface-3);border:1px solid var(--border);
border-radius:0 0 var(--radius) var(--radius);
padding:1.25rem;overflow-x:auto;
font-family:'Fira Code',monospace;font-size:.82rem;line-height:1.7;
}
.code-wrap:first-of-type pre,.code-wrap:only-child pre{
border-radius:var(--radius);
}
/* Syntax colours */
.kw{color:#c084fc} /* keyword */
.fn{color:#60a5fa} /* function */
.str{color:#34d399} /* string */
.cm{color:#4b5563;font-style:italic} /* comment */
.num{color:#fb923c} /* number */
.cls{color:#f9a8d4} /* class */
.prop{color:#93c5fd} /* property */
.op{color:#e2e8f0} /* operator */
/* Annotated code */
.ann-line{
display:flex;gap:1rem;align-items:flex-start;
padding:.2rem .5rem;border-radius:6px;
transition:background .15s;cursor:default;
}
.ann-line:hover{background:rgba(139,92,246,.08)}
.ann-line code{flex:1;font-family:'Fira Code',monospace;font-size:.82rem}
.ann-note{
font-size:.72rem;color:var(--purple-l);
white-space:nowrap;padding-top:.15rem;
opacity:0;transition:opacity .2s;
}
.ann-line:hover .ann-note{opacity:1}
/* ── Tabs ──────────────────────────────────────────────── */
.tabs{display:flex;gap:.4rem;margin-bottom:1rem;flex-wrap:wrap}
.tab-btn{
padding:.45rem 1rem;border-radius:8px;
background:var(--surface-2);border:1px solid var(--border);
color:var(--text-2);font-family:inherit;font-size:.82rem;font-weight:600;
cursor:pointer;transition:all .15s;
}
.tab-btn.active{
background:var(--grad-1);border-color:transparent;color:#fff;
}
.tab-btn:hover:not(.active){background:var(--surface-3);color:var(--text)}
.tab-panel{display:none}
.tab-panel.active{display:block}
/* ── RBAC Demo ─────────────────────────────────────────── */
.demo-grid{
display:grid;
grid-template-columns:1fr 1fr;
gap:1rem;
}
@media(max-width:620px){.demo-grid{grid-template-columns:1fr}}
.demo-panel{
background:var(--surface-2);border:1px solid var(--border);
border-radius:var(--radius);padding:1.1rem;
}
.demo-panel h4{
font-size:.78rem;font-weight:700;text-transform:uppercase;
letter-spacing:.08em;color:var(--text-3);margin-bottom:.85rem;
}
.role-tag{
display:inline-flex;align-items:center;gap:.3rem;
background:rgba(139,92,246,.15);border:1px solid rgba(139,92,246,.3);
color:var(--purple-l);font-size:.75rem;font-weight:600;
padding:.2rem .6rem;border-radius:6px;margin:.2rem;cursor:pointer;
transition:all .15s;
}
.role-tag.selected,.role-tag:hover{
background:rgba(139,92,246,.35);border-color:var(--purple);
}
.perm-chip{
display:inline-flex;align-items:center;gap:.25rem;
font-size:.72rem;font-weight:600;
padding:.15rem .55rem;border-radius:20px;margin:.15rem;
}
.perm-read {background:rgba(59,130,246,.15);color:var(--blue-l)}
.perm-write{background:rgba(16,185,129,.15);color:var(--green-l)}
.perm-delete{background:rgba(239,68,68,.15);color:var(--red)}
.perm-admin{background:rgba(245,158,11,.15);color:var(--amber)}
.check-row{
display:flex;align-items:center;gap:.75rem;
padding:.6rem .85rem;border-radius:8px;
background:var(--surface-3);margin-bottom:.5rem;
}
.check-row select,.check-row input{
background:var(--surface);border:1px solid var(--border);
color:var(--text);font-family:inherit;font-size:.82rem;
padding:.35rem .65rem;border-radius:6px;flex:1;min-width:0;
outline:none;
}
.check-row select:focus,.check-row input:focus{border-color:var(--purple)}
.check-btn{
padding:.4rem .9rem;border-radius:8px;
background:var(--grad-1);border:none;
color:#fff;font-family:inherit;font-size:.82rem;font-weight:700;
cursor:pointer;white-space:nowrap;flex-shrink:0;
transition:opacity .15s;
}
.check-btn:hover{opacity:.85}
.result-box{
padding:.75rem 1rem;border-radius:8px;
font-size:.875rem;font-weight:600;
display:flex;align-items:center;gap:.6rem;
transition:all .3s;
}
.result-allow{background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);color:var(--green-l)}
.result-deny {background:rgba(239,68,68,.12); border:1px solid rgba(239,68,68,.3); color:var(--red)}
.result-idle {background:var(--surface-3);border:1px solid var(--border);color:var(--text-3)}
/* Inheritance tree */
.tree{font-family:'Fira Code',monospace;font-size:.82rem;line-height:2}
.tree-node{display:flex;align-items:center;gap:.5rem;padding:.1rem 0}
.tree-indent{padding-left:1.5rem;border-left:2px solid var(--border);margin-left:.6rem}
.tree-arrow{color:var(--purple-l);font-size:.9rem}
/* ── Mistakes ──────────────────────────────────────────── */
.mistake-card{
background:var(--surface);border:1px solid rgba(239,68,68,.25);
border-left:4px solid var(--red);
border-radius:var(--radius);padding:1.25rem;margin-bottom:.75rem;
}
.mistake-card h4{
color:var(--red);font-size:.9rem;font-weight:700;
display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;
}
.fix-card{
background:rgba(16,185,129,.06);border:1px solid rgba(16,185,129,.2);
border-left:4px solid var(--green);
border-radius:var(--radius);padding:1rem;margin-top:.6rem;
}
.fix-card p{font-size:.82rem;color:var(--green-l)}
/* ── Tips ──────────────────────────────────────────────── */
.tip-card{
background:var(--surface);border:1px solid rgba(139,92,246,.25);
border-left:4px solid var(--purple);
border-radius:var(--radius);padding:1.25rem;margin-bottom:.75rem;
display:flex;gap:1rem;
}
.tip-icon{font-size:1.5rem;flex-shrink:0;margin-top:.1rem}
.tip-card h4{font-size:.9rem;font-weight:700;color:var(--purple-l);margin-bottom:.4rem}
.tip-card p{font-size:.83rem;color:var(--text-2);line-height:1.65}
/* ── Quiz ──────────────────────────────────────────────── */
.quiz-card{
background:var(--surface);border:1px solid var(--border);
border-radius:var(--radius);padding:1.5rem;margin-bottom:1rem;
}
.quiz-q{font-size:.95rem;font-weight:700;margin-bottom:1rem}
.quiz-q span{color:var(--purple-l)}
.quiz-opts{display:flex;flex-direction:column;gap:.5rem}
.quiz-opt{
display:flex;align-items:center;gap:.75rem;
padding:.75rem 1rem;border-radius:10px;
background:var(--surface-2);border:1.5px solid var(--border);
cursor:pointer;transition:all .15s;font-size:.875rem;
}
.quiz-opt:hover{border-color:var(--purple);background:var(--surface-3)}
.quiz-opt.correct{background:rgba(16,185,129,.12);border-color:var(--green);color:var(--green-l)}
.quiz-opt.wrong {background:rgba(239,68,68,.12); border-color:var(--red); color:var(--red)}
.quiz-opt.disabled{pointer-events:none;opacity:.6}
.quiz-opt.correct.disabled{opacity:1}
.quiz-feedback{
margin-top:.85rem;padding:.75rem 1rem;border-radius:8px;
font-size:.83rem;font-weight:600;display:none;
}
.quiz-feedback.show{display:flex;align-items:center;gap:.5rem}
.quiz-feedback.ok {background:rgba(16,185,129,.1);color:var(--green-l)}
.quiz-feedback.no {background:rgba(239,68,68,.1); color:var(--red)}
.quiz-score{
text-align:center;padding:1.5rem;
background:var(--surface);border:1px solid var(--border);
border-radius:var(--radius);display:none;
}
.quiz-score .score-num{
font-size:3rem;font-weight:900;
background:var(--grad-1);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
}
/* Confetti canvas */
#confetti-canvas{
position:fixed;inset:0;pointer-events:none;z-index:9998;
}
/* ── Cheat sheet ───────────────────────────────────────── */
.cheat-grid{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
gap:1rem;
}
.cheat-item{
background:var(--surface-2);border:1px solid var(--border);
border-radius:var(--radius);padding:1rem;
}
.cheat-item h4{
font-size:.72rem;font-weight:700;text-transform:uppercase;
letter-spacing:.08em;color:var(--text-3);margin-bottom:.6rem;
}
.cheat-item ul{list-style:none;display:flex;flex-direction:column;gap:.35rem}
.cheat-item li{font-size:.8rem;color:var(--text-2);display:flex;gap:.5rem}
.cheat-item li::before{content:"→";color:var(--purple-l);flex-shrink:0}
/* ── Footer ────────────────────────────────────────────── */
footer{
background:var(--surface);border-top:1px solid var(--border);
padding:2.5rem 1.25rem;text-align:center;
color:var(--text-3);font-size:.8rem;
}
footer .footer-brand{
font-size:1.1rem;font-weight:800;
background:var(--grad-1);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;margin-bottom:.5rem;
}
/* ── Animations ────────────────────────────────────────── */
@keyframes fadeUp{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:none}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
@keyframes shimmer{
0%{background-position:-200% 0}
100%{background-position:200% 0}
}
.fade-up{opacity:0;animation:fadeUp .6s ease forwards}
.delay-1{animation-delay:.1s}.delay-2{animation-delay:.2s}
.delay-3{animation-delay:.3s}.delay-4{animation-delay:.4s}
.float-anim{animation:float 3s ease-in-out infinite}
@media(prefers-reduced-motion:reduce){
*{animation-duration:.01ms!important;transition-duration:.01ms!important}
}
/* ── Responsive ────────────────────────────────────────── */
@media(max-width:640px){
.hero h1{font-size:1.9rem}
.nav-links{display:none}
.demo-grid{grid-template-columns:1fr}
.cheat-grid{grid-template-columns:1fr}
}
</style>
</head>
<body>
<div id="progress-bar" role="progressbar" aria-label="Reading progress"></div>
<canvas id="confetti-canvas" aria-hidden="true"></canvas>
<!-- ░░ NAV ░░ -->
<nav aria-label="Tutorial navigation">
<div class="nav-inner">
<span class="nav-brand">🔐 RBAC Tutorial</span>
<ul class="nav-links" role="list">
<li><a href="#what">What</a></li>
<li><a href="#why">Why</a></li>
<li><a href="#how">How</a></li>
<li><a href="#demo">Demo</a></li>
<li><a href="#code">Code</a></li>
<li><a href="#mistakes">Mistakes</a></li>
<li><a href="#tips">Tips</a></li>
<li><a href="#quiz">Quiz</a></li>
<li><a href="#cheat">Cheat Sheet</a></li>
</ul>
</div>
</nav>
<!-- ░░ HERO ░░ -->
<header class="hero" id="top">
<div class="hero-glow" aria-hidden="true"></div>
<div class="container">
<div class="hero-eyebrow fade-up">
<span class="badge badge-purple">🎓 Interactive Tutorial</span>
<span class="badge badge-blue" style="margin-left:.5rem">⏱ 8 min read</span>
<span class="badge badge-green" style="margin-left:.5rem">🎮 3 interactive demos</span>
</div>
<h1 class="fade-up delay-1">
Role-Based<br/>
<span class="grad-text">Access Control</span>
</h1>
<p class="hero-sub fade-up delay-2">
Learn how to build a <strong>RBAC system</strong> — the security backbone of every serious web app.
Roles, permissions, inheritance, and request evaluation — all explained simply. 🚀
</p>
<div class="hero-stats fade-up delay-3">
<div class="hero-stat">
<span class="hero-stat-num grad-text">3</span>
<span class="hero-stat-lbl">Core Concepts</span>
</div>
<div class="hero-stat">
<span class="hero-stat-num" style="color:var(--blue-l)">∞</span>
<span class="hero-stat-lbl">Scalability</span>
</div>
<div class="hero-stat">
<span class="hero-stat-num" style="color:var(--green-l)">100%</span>
<span class="hero-stat-lbl">Industry Standard</span>
</div>
<div class="hero-stat">
<span class="hero-stat-num" style="color:var(--pink)">🔐</span>
<span class="hero-stat-lbl">Secure by Design</span>
</div>
</div>
</div>
</header>
<main>
<!-- ══════════════════════════════════════════════════════════
SECTION 1 — WHAT IS IT?
══════════════════════════════════════════════════════════ -->
<section id="what">
<div class="container">
<div class="section-header">
<div class="section-icon" style="background:rgba(139,92,246,.15)">🤔</div>
<h2>What Is It?</h2>
</div>
<div class="card" style="margin-bottom:1.25rem">
<p style="font-size:1.05rem;line-height:1.8;margin-bottom:1rem">
Imagine a <strong>hotel</strong>. 🏨 A <em>guest</em> can open their own room.
A <em>cleaner</em> can open any room but not the safe.
A <em>manager</em> can do everything.
Nobody needs a separate key for every single door — instead, their
<strong>role</strong> determines what they can access.
</p>
<p style="font-size:.9rem;color:var(--text-2)">
<strong>Role-Based Access Control (RBAC)</strong> works the same way in software.
Instead of granting permissions directly to individual users, you define
<em>roles</em> (like <code>admin</code>, <code>editor</code>, <code>viewer</code>),
attach <em>permissions</em> to those roles, and then assign roles to users.
When a user makes a request, the system checks: <em>"Does this user's role
allow this action on this resource?"</em>
</p>
</div>
<div class="card-grid">
<div class="feature-card">
<div class="icon">👤</div>
<h3>User</h3>
<p>A person or service making requests. Has one or more roles assigned.</p>
</div>
<div class="feature-card">
<div class="icon">🎭</div>
<h3>Role</h3>
<p>A named collection of permissions. e.g. <code>editor</code> = can read + write articles.</p>
</div>
<div class="feature-card">
<div class="icon">🔑</div>
<h3>Permission</h3>
<p>A specific allowed action on a resource. Format: <code>resource:action</code> e.g. <code>invoice:read</code>.</p>
</div>
<div class="feature-card">
<div class="icon">📦</div>
<h3>Resource</h3>
<p>The thing being accessed — an invoice, a user record, a report, an API endpoint.</p>
</div>
<div class="feature-card">
<div class="icon">⚡</div>
<h3>Action</h3>
<p>What you want to do: <code>read</code>, <code>write</code>, <code>delete</code>, <code>publish</code>.</p>
</div>
<div class="feature-card">
<div class="icon">🧬</div>
<h3>Inheritance</h3>
<p>A senior role automatically includes all permissions of a junior role it extends.</p>
</div>
</div>
</div>
</section>
<!-- ══════════════════════════════════════════════════════════
SECTION 2 — WHY USE IT?
══════════════════════════════════════════════════════════ -->
<section id="why" style="background:var(--surface);border-top:1px solid var(--border);border-bottom:1px solid var(--border)">
<div class="container">
<div class="section-header">
<div class="section-icon" style="background:rgba(59,130,246,.15)">💡</div>
<h2>Why Use It?</h2>
</div>
<div class="card-grid">
<div class="feature-card">
<div class="icon">🔒</div>
<h3>Least Privilege</h3>
<p>Users only get exactly what they need — nothing more. A billing clerk can't delete users.</p>
</div>
<div class="feature-card">
<div class="icon">📈</div>
<h3>Scales Easily</h3>
<p>Add 1000 new editors? Assign them the <code>editor</code> role. One change, done.</p>
</div>
<div class="feature-card">
<div class="icon">🛡️</div>
<h3>Audit-Friendly</h3>
<p>Easy to answer "who can do what?" — critical for compliance (GDPR, SOC 2, HIPAA).</p>
</div>
<div class="feature-card">
<div class="icon">🧩</div>
<h3>Separation of Duties</h3>
<p>No single user has unchecked power. Approvers can't also be requesters.</p>
</div>
<div class="feature-card">
<div class="icon">🔄</div>
<h3>Easy Revocation</h3>
<p>Remove a role from a user and all associated permissions vanish instantly.</p>
</div>
<div class="feature-card">
<div class="icon">🏗️</div>
<h3>Industry Standard</h3>
<p>Used by AWS IAM, GitHub, Google Cloud, Kubernetes, and virtually every SaaS product.</p>
</div>
</div>
</div>
</section>
<!-- ══════════════════════════════════════════════════════════
SECTION 3 — HOW DOES IT WORK?
══════════════════════════════════════════════════════════ -->
<section id="how">
<div class="container">
<div class="section-header">
<div class="section-icon" style="background:rgba(16,185,129,.15)">⚙️</div>
<h2>How Does It Work?</h2>
</div>
<p style="color:var(--text-2);margin-bottom:1.5rem;font-size:.9rem">
Click each step to expand the full explanation 👇
</p>
<div class="accordion" id="how-accordion">
<div class="acc-item">
<button class="acc-trigger" aria-expanded="false" aria-controls="step1-body">
<div style="display:flex;align-items:center;gap:.75rem">
<span class="step-num">1</span>
<span>Define Permissions — the atomic unit</span>
</div>
<span class="chevron" aria-hidden="true">▾</span>
</button>
<div class="acc-body" id="step1-body" role="region">
<div class="acc-body-inner">
<p>A <strong>permission</strong> is the smallest unit of access. It combines a <em>resource</em> and an <em>action</em> with a colon separator:</p>
<div class="code-wrap">
<div class="code-label"><span>Permission format</span></div>
<pre><span class="cm">// resource:action</span>
<span class="str">"invoice:read"</span> <span class="cm">// can view invoices</span>
<span class="str">"invoice:write"</span> <span class="cm">// can create/edit invoices</span>
<span class="str">"invoice:delete"</span> <span class="cm">// can delete invoices</span>
<span class="str">"user:read"</span> <span class="cm">// can view users</span>
<span class="str">"user:write"</span> <span class="cm">// can create/edit users</span>
<span class="str">"report:publish"</span> <span class="cm">// can publish reports</span></pre>
</div>
<p>Think of each permission as a <strong>key</strong>. A role is a <strong>keyring</strong> holding several keys.</p>
</div>
</div>
</div>
<div class="acc-item">
<button class="acc-trigger" aria-expanded="false" aria-controls="step2-body">
<div style="display:flex;align-items:center;gap:.75rem">
<span class="step-num">2</span>
<span>Define Roles — named permission collections</span>
</div>
<span class="chevron" aria-hidden="true">▾</span>
</button>
<div class="acc-body" id="step2-body" role="region">
<div class="acc-body-inner">
<p>A <strong>role</strong> is a named set of permissions. You can also declare <em>inherits</em> to build a hierarchy:</p>
<div class="code-wrap">
<div class="code-label"><span>Role definitions (JS object)</span></div>
<pre><span class="kw">const</span> <span class="prop">roles</span> = {
<span class="str">viewer</span>: {
<span class="prop">permissions</span>: [<span class="str">"invoice:read"</span>, <span class="str">"report:read"</span>]
},
<span class="str">editor</span>: {
<span class="prop">inherits</span>: [<span class="str">"viewer"</span>], <span class="cm">// gets all viewer perms</span>
<span class="prop">permissions</span>: [<span class="str">"invoice:write"</span>, <span class="str">"report:write"</span>]
},
<span class="str">admin</span>: {
<span class="prop">inherits</span>: [<span class="str">"editor"</span>], <span class="cm">// gets all editor perms</span>
<span class="prop">permissions</span>: [<span class="str">"user:write"</span>, <span class="str">"invoice:delete"</span>]
}
};</pre>
</div>
<p>The <code>admin</code> role ends up with <em>every</em> permission from <code>viewer</code> + <code>editor</code> + its own — automatically, via inheritance.</p>
</div>
</div>
</div>
<div class="acc-item">
<button class="acc-trigger" aria-expanded="false" aria-controls="step3-body">
<div style="display:flex;align-items:center;gap:.75rem">
<span class="step-num">3</span>
<span>Assign Roles to Users</span>
</div>
<span class="chevron" aria-hidden="true">▾</span>
</button>
<div class="acc-body" id="step3-body" role="region">
<div class="acc-body-inner">
<p>Each user record stores an array of role names. A user can hold <em>multiple</em> roles simultaneously — their effective permissions are the <strong>union</strong> of all assigned roles.</p>
<div class="code-wrap">
<div class="code-label"><span>User records</span></div>
<pre><span class="kw">const</span> <span class="prop">users</span> = {
alice: { <span class="prop">roles</span>: [<span class="str">"admin"</span>] },
bob: { <span class="prop">roles</span>: [<span class="str">"editor"</span>] },
carol: { <span class="prop">roles</span>: [<span class="str">"viewer"</span>] },
dave: { <span class="prop">roles</span>: [<span class="str">"viewer"</span>, <span class="str">"editor"</span>] } <span class="cm">// multi-role</span>
};</pre>
</div>
</div>
</div>
</div>
<div class="acc-item">
<button class="acc-trigger" aria-expanded="false" aria-controls="step4-body">
<div style="display:flex;align-items:center;gap:.75rem">
<span class="step-num">4</span>
<span>Resolve Permissions — flatten the inheritance tree</span>
</div>
<span class="chevron" aria-hidden="true">▾</span>
</button>
<div class="acc-body" id="step4-body" role="region">
<div class="acc-body-inner">
<p>When a request arrives, collect all permissions for the user by walking up the inheritance chain recursively, deduplicating as you go:</p>
<div class="code-wrap">
<div class="code-label"><span>Resolve function</span></div>
<pre><span class="kw">function</span> <span class="fn">resolvePermissions</span>(roleName, visited = <span class="kw">new</span> <span class="cls">Set</span>()) {
<span class="kw">if</span> (visited.<span class="fn">has</span>(roleName)) <span class="kw">return</span> []; <span class="cm">// guard cycles</span>
visited.<span class="fn">add</span>(roleName);
<span class="kw">const</span> role = roles[roleName];
<span class="kw">if</span> (!role) <span class="kw">return</span> [];
<span class="kw">let</span> perms = [...(role.permissions ?? [])];
<span class="kw">for</span> (<span class="kw">const</span> parent <span class="kw">of</span> (role.inherits ?? [])) {
perms = perms.<span class="fn">concat</span>(<span class="fn">resolvePermissions</span>(parent, visited));
}
<span class="kw">return</span> [...<span class="kw">new</span> <span class="cls">Set</span>(perms)]; <span class="cm">// deduplicate</span>
}</pre>
</div>
</div>
</div>
</div>
<div class="acc-item">
<button class="acc-trigger" aria-expanded="false" aria-controls="step5-body">
<div style="display:flex;align-items:center;gap:.75rem">
<span class="step-num">5</span>
<span>Evaluate the Request — allow or deny</span>
</div>
<span class="chevron" aria-hidden="true">▾</span>
</button>
<div class="acc-body" id="step5-body" role="region">
<div class="acc-body-inner">
<p>On every incoming request, build the user's full permission set and check whether the required permission is present. Default is <strong>deny</strong>.</p>
<div class="code-wrap">
<div class="code-label"><span>canDo() — the gate function</span></div>
<pre><span class="kw">function</span> <span class="fn">canDo</span>(userId, resource, action) {
<span class="kw">const</span> user = users[userId];
<span class="kw">if</span> (!user) <span class="kw">return false</span>;
<span class="cm">// Collect all permissions across all assigned roles</span>
<span class="kw">const</span> allPerms = user.roles
.<span class="fn">flatMap</span>(r => <span class="fn">resolvePermissions</span>(r));
<span class="kw">const</span> required = <span class="str">`</span><span class="op">${</span>resource<span class="op">}</span><span class="str">:</span><span class="op">${</span>action<span class="op">}</span><span class="str">`</span>;
<span class="kw">return</span> allPerms.<span class="fn">includes</span>(required);
}
<span class="cm">// Usage on an API route:</span>
<span class="kw">if</span> (!<span class="fn">canDo</span>(req.userId, <span class="str">"invoice"</span>, <span class="str">"delete"</span>)) {
<span class="kw">return</span> res.<span class="fn">status</span>(<span class="num">403</span>).<span class="fn">json</span>({ error: <span class="str">"Forbidden"</span> });
}</pre>
</div>
</div>
</div>
</div>
</div><!-- /accordion -->
<!-- Inheritance tree visual -->
<div class="card" style="margin-top:1.5rem">
<h3 style="font-size:.9rem;font-weight:700;margin-bottom:1rem;color:var(--text-2)">
🧬 Inheritance Tree — how permissions flow upward
</h3>
<div class="tree">
<div class="tree-node">
<span class="badge badge-purple">admin</span>
<span style="color:var(--text-3);font-size:.75rem;margin-left:.5rem">user:write, invoice:delete</span>
</div>
<div class="tree-indent">
<div class="tree-node">
<span class="tree-arrow">↳</span>
<span class="badge badge-blue">editor</span>
<span style="color:var(--text-3);font-size:.75rem;margin-left:.5rem">invoice:write, report:write</span>
</div>
<div class="tree-indent">
<div class="tree-node">
<span class="tree-arrow">↳</span>
<span class="badge badge-green">viewer</span>
<span style="color:var(--text-3);font-size:.75rem;margin-left:.5rem">invoice:read, report:read</span>
</div>
</div>
</div>
</div>
<p style="font-size:.78rem;color:var(--text-3);margin-top:.85rem">
✅ An <strong>admin</strong> automatically inherits <em>all 6 permissions</em> from the chain above.
</p>
</div>
</div>
</section>
<!-- ══════════════════════════════════════════════════════════
SECTION 4 — LIVE DEMO
══════════════════════════════════════════════════════════ -->
<section id="demo" style="background:var(--surface);border-top:1px solid var(--border);border-bottom:1px solid var(--border)">
<div class="container">
<div class="section-header">
<div class="section-icon" style="background:rgba(236,72,153,.15)">🎮</div>
<h2>Live Demo</h2>
</div>
<p style="color:var(--text-2);margin-bottom:1.5rem;font-size:.9rem">
Select a user, pick a resource and action, then hit <strong>Check Access</strong> to see RBAC in action. ⚡
</p>
<div class="demo-grid">
<!-- Left: Role map -->
<div class="demo-panel">
<h4>👥 Users & Their Roles</h4>
<div id="user-role-map"></div>
<div style="margin-top:1rem">
<h4 style="margin-bottom:.6rem">🔑 Resolved Permissions</h4>
<div id="resolved-perms" style="min-height:32px"></div>
</div>
</div>
<!-- Right: Access check -->
<div class="demo-panel">
<h4>🔍 Check Access</h4>
<div class="check-row">
<select id="demo-user" aria-label="Select user">
<option value="alice">alice (admin)</option>
<option value="bob">bob (editor)</option>
<option value="carol">carol (viewer)</option>
<option value="dave">dave (viewer+editor)</option>
</select>
</div>
<div class="check-row">
<select id="demo-resource" aria-label="Select resource">
<option value="invoice">invoice</option>
<option value="report">report</option>
<option value="user">user</option>
</select>
<select id="demo-action" aria-label="Select action">
<option value="read">read</option>
<option value="write">write</option>
<option value="delete">delete</option>
</select>
</div>
<button class="check-btn" id="demo-check-btn" onclick="runDemoCheck()">
⚡ Check Access
</button>
<div id="demo-result" class="result-box result-idle" style="margin-top:.85rem" aria-live="polite">
Select options and click Check Access
</div>
<div style="margin-top:1rem">
<h4 style="margin-bottom:.5rem">📋 Permission required:</h4>
<code id="demo-perm-required" style="font-family:'Fira Code',monospace;font-size:.85rem;color:var(--purple-l)">invoice:read</code>
</div>
</div>
</div>
</div>
</section>
<!-- ══════════════════════════════════════════════════════════
SECTION 5 — CODE BREAKDOWN
══════════════════════════════════════════════════════════ -->
<section id="code">
<div class="container">
<div class="section-header">
<div class="section-icon" style="background:rgba(59,130,246,.15)">💻</div>
<h2>Code Breakdown</h2>
</div>
<div class="tabs" role="tablist" aria-label="Code examples">
<button class="tab-btn active" role="tab" aria-selected="true" data-tab="tab-full">Full System</button>
<button class="tab-btn" role="tab" aria-selected="false" data-tab="tab-middleware">Middleware</button>
<button class="tab-btn" role="tab" aria-selected="false" data-tab="tab-db">Database Schema</button>
</div>
<!-- Tab: Full System -->
<div class="tab-panel active" id="tab-full">
<div class="code-wrap">
<div class="code-label">
<span>rbac.js — Complete annotated implementation</span>
<button class="copy-btn" onclick="copyCode('code-full')">Copy</button>
</div>
<pre id="code-full"><span class="cm">// ── 1. Permission definitions ──────────────────────────────</span>
<span class="cm">// A permission = "resource:action" string</span>
<span class="kw">const</span> <span class="prop">PERMISSIONS</span> = {
INVOICE_READ: <span class="str">'invoice:read'</span>,
INVOICE_WRITE: <span class="str">'invoice:write'</span>,
INVOICE_DELETE: <span class="str">'invoice:delete'</span>,
REPORT_READ: <span class="str">'report:read'</span>,
REPORT_WRITE: <span class="str">'report:write'</span>,
USER_READ: <span class="str">'user:read'</span>,
USER_WRITE: <span class="str">'user:write'</span>,
};
<span class="cm">// ── 2. Role definitions with inheritance ──────────────────</span>
<span class="kw">const</span> <span class="prop">ROLES</span> = {
viewer: {
permissions: [
PERMISSIONS.INVOICE_READ,
PERMISSIONS.REPORT_READ,
PERMISSIONS.USER_READ,
],
},
editor: {
inherits: [<span class="str">'viewer'</span>], <span class="cm">// ← inherits viewer's perms</span>
permissions: [
PERMISSIONS.INVOICE_WRITE,
PERMISSIONS.REPORT_WRITE,
],
},
admin: {
inherits: [<span class="str">'editor'</span>], <span class="cm">// ← inherits editor + viewer</span>
permissions: [
PERMISSIONS.INVOICE_DELETE,
PERMISSIONS.USER_WRITE,
],
},
};
<span class="cm">// ── 3. Resolve all permissions for a role (recursive) ─────</span>
<span class="kw">function</span> <span class="fn">resolvePermissions</span>(roleName, visited = <span class="kw">new</span> <span class="cls">Set</span>()) {
<span class="kw">if</span> (visited.<span class="fn">has</span>(roleName)) <span class="kw">return</span> []; <span class="cm">// prevent cycles</span>
visited.<span class="fn">add</span>(roleName);
<span class="kw">const</span> role = ROLES[roleName];
<span class="kw">if</span> (!role) <span class="kw">return</span> [];
<span class="kw">let</span> perms = [...(role.permissions ?? [])];
<span class="kw">for</span> (<span class="kw">const</span> parentRole <span class="kw">of</span> (role.inherits ?? [])) {
perms = perms.<span class="fn">concat</span>(<span class="fn">resolvePermissions</span>(parentRole, visited));
}
<span class="kw">return</span> [...<span class="kw">new</span> <span class="cls">Set</span>(perms)]; <span class="cm">// deduplicate</span>
}
<span class="cm">// ── 4. Check if a user can perform an action ──────────────</span>
<span class="kw">function</span> <span class="fn">canDo</span>(user, resource, action) {
<span class="kw">if</span> (!user?.roles?.length) <span class="kw">return false</span>;
<span class="kw">const</span> effectivePerms = user.roles
.<span class="fn">flatMap</span>(role => <span class="fn">resolvePermissions</span>(role));
<span class="kw">return</span> effectivePerms.<span class="fn">includes</span>(<span class="str">`</span><span class="op">${</span>resource<span class="op">}</span><span class="str">:</span><span class="op">${</span>action<span class="op">}</span><span class="str">`</span>);
}
<span class="cm">// ── 5. Example usage ──────────────────────────────────────</span>
<span class="kw">const</span> alice = { id: <span class="str">'alice'</span>, roles: [<span class="str">'admin'</span>] };
<span class="kw">const</span> carol = { id: <span class="str">'carol'</span>, roles: [<span class="str">'viewer'</span>] };
console.<span class="fn">log</span>(<span class="fn">canDo</span>(alice, <span class="str">'invoice'</span>, <span class="str">'delete'</span>)); <span class="cm">// true</span>
console.<span class="fn">log</span>(<span class="fn">canDo</span>(carol, <span class="str">'invoice'</span>, <span class="str">'delete'</span>)); <span class="cm">// false</span>
console.<span class="fn">log</span>(<span class="fn">canDo</span>(carol, <span class="str">'invoice'</span>, <span class="str">'read'</span>)); <span class="cm">// true</span></pre>
</div>
</div>
<!-- Tab: Middleware -->
<div class="tab-panel" id="tab-middleware">
<div class="code-wrap">
<div class="code-label">
<span>Express.js middleware — protect routes</span>
<button class="copy-btn" onclick="copyCode('code-mw')">Copy</button>
</div>
<pre id="code-mw"><span class="cm">// requirePermission middleware factory</span>
<span class="kw">function</span> <span class="fn">requirePermission</span>(resource, action) {
<span class="kw">return</span> (req, res, next) => {
<span class="kw">const</span> user = req.user; <span class="cm">// set by auth middleware</span>
<span class="kw">if</span> (!user) {
<span class="kw">return</span> res.<span class="fn">status</span>(<span class="num">401</span>).<span class="fn">json</span>({ error: <span class="str">'Unauthenticated'</span> });
}
<span class="kw">if</span> (!<span class="fn">canDo</span>(user, resource, action)) {
<span class="kw">return</span> res.<span class="fn">status</span>(<span class="num">403</span>).<span class="fn">json</span>({
error: <span class="str">'Forbidden'</span>,
required: <span class="str">`</span><span class="op">${</span>resource<span class="op">}</span><span class="str">:</span><span class="op">${</span>action<span class="op">}</span><span class="str">`</span>,
});
}
<span class="fn">next</span>(); <span class="cm">// ✅ permitted — continue to route handler</span>
};
}
<span class="cm">// Apply to routes:</span>
app.<span class="fn">get</span>(<span class="str">'/invoices'</span>,
<span class="fn">requirePermission</span>(<span class="str">'invoice'</span>, <span class="str">'read'</span>),
invoiceController.list
);
app.<span class="fn">delete</span>(<span class="str">'/invoices/:id'</span>,
<span class="fn">requirePermission</span>(<span class="str">'invoice'</span>, <span class="str">'delete'</span>),
invoiceController.remove
);
app.<span class="fn
Live Preview