../courses.php
CRM
CRM101
90
CRM Permissions And Roles
Learn why not every person should see or change every CRM record.
A "Delete Contact" button is hidden from regular staff with
display: none; in CSS — only managers are supposed to see it. A regular staff member opens their browser's developer tools, finds the button, and deletes a contact anyway. How was that possible if the button was genuinely hidden?
display: none; hides an element visually — the button's actual HTML is still sent to the browser and still sitting in the page, fully intact, just not rendered on screen. Anyone can open developer tools, find the button in the page's source, and either click it directly through the inspector or copy its underlying request and send it manually. Hiding something from view and actually preventing it from happening are two completely different things, and only one of them is real security.
A CRM with real customer data needs permissions — not everyone who can open the page should be able to delete a contact, see private notes, or change someone's status. This lesson builds the honest version of that, including the version that doesn't actually work.
Hiding an element with CSS, or removing it from the page with JavaScript after the page has already loaded, only ever changes what's displayed. The HTML for that element was already sent to the browser before any of that hiding happened — anyone looking at the page's source, not just what's rendered, can still find it.
Real enforcement has to happen on the server, before the dangerous action is actually performed — checking, in PHP, whether the current request is actually allowed to do this, and refusing to act if it isn't, regardless of what the browser displayed or didn't display beforehand.
This course's CRM, deliberately kept simple, doesn't have real user accounts or login — which means it genuinely can't enforce permissions properly yet. This lesson demonstrates the gap honestly rather than implying a hidden button is sufficient.
This lesson shows the fake version — a hidden button — actually getting bypassed, names exactly why, and points clearly at what real enforcement would require, without pretending this simple CRM has solved a problem it genuinely hasn't.
A hidden button, and proof it isn't actually protected
<?php $isManager = false; // pretend this came from a real login ?>
<?php if ($isManager) { ?>
<form method="POST">
<input type="hidden" name="contact_index" value="0">
<button type="submit" name="delete_contact">Delete</button>
</form>
<?php } ?>
<?php
// THE ACTUAL GAP: if $isManager were only checked here
// (in the display logic above) and NOT checked again below,
// before actually performing the delete, anyone could still
// submit the same form fields directly and have it work:
if (isset($_POST['delete_contact'])) {
// missing: a real check of who is actually making this request
unset($contacts[$_POST['contact_index']]);
}
?>
$isManager = false; — Marked plainly as a stand-in for what a real login system would actually determine — this simple CRM has no real authentication, and pretending otherwise here would be dishonest about what's actually being demonstrated.
<?php if ($isManager) { ?> — This only controls whether the delete button is rendered into the page at all — it says nothing about whether deleting actually happens once a request arrives.
"THE ACTUAL GAP" comment — Names the missing piece directly: the code that actually performs the delete never checks
$isManager at all — it just trusts that the
delete_contact field showing up in
$_POST means it's allowed.
unset($contacts[$_POST['contact_index']]); — Runs unconditionally the moment the right POST field is present, regardless of who actually sent the request or whether they were ever shown the delete button at all.
What real enforcement would need, named but not built here — A genuine fix checks
$isManager a second time, right here, immediately before
unset() runs — and that check has to come from something the visitor can't fake, like a real server-side login session, which this simple course's CRM was never built to include.
A hidden button's HTML is still right there in the page source
Opening a browser's "View Source" or developer tools on this exact page shows the delete button's full HTML, completely intact, even with the CSS or conditional logic hiding it from normal view — nothing about visual hiding removes it from what was actually sent.
A request can be replayed without ever touching the visible page at all
Someone who understands what fields a form submits doesn't even need the button to be visible, or present in the page at all — they can construct and send the exact same request directly, which is exactly why display-layer hiding was never real protection in the first place.
Checking permission only in the display logic, and not again before the action, is the precise bug
The code in this lesson checks
$isManager exactly once — to decide whether to show a button — and never again before actually deleting something. That single missing second check is the entire vulnerability, named specifically rather than left vague.
This simple CRM was never built with real authentication, and that's stated honestly here rather than implied otherwise
Every earlier lecture in this course deliberately stayed within what one person, with no login system, could safely build and use — pretending this lesson somehow added real security on top of that would contradict everything the rest of the course was honest about.
This is the exact kind of gap a contract-and-test mindset would catch before shipping
"Does the delete action check permission immediately before it runs, not just when deciding what to display" is precisely the kind of specific, checkable question this catalog's AI course teaches asking before trusting any piece of code is actually safe to use.
Checking permission only for display — avoid
<?php if ($isManager) { ?>
<button>Delete</button>
<?php } ?>
if (isset($_POST['delete_contact'])) {
unset($contacts[$_POST['contact_index']]);
}
Checking permission again before the action — the real fix this lesson points toward
if (isset($_POST['delete_contact'])) {
if ($isManager) {
unset($contacts[$_POST['contact_index']]);
}
// a real version needs $isManager from an actual login,
// not a hardcoded value — this lesson stops short of that
}
Paste your PHP delete or edit logic into ChatGPT and ask: "Does this code check permission immediately before performing the action, or only when deciding what to display?" It's good at spotting display-only checks, though actually opening DevTools yourself and confirming a hidden button is still in the page's HTML is worth doing directly.
Build the hidden delete button from this lesson, with
$isManager set to false.
Open your browser's developer tools and confirm the delete button's HTML is still present in the page, even though it's not visible.
Manually edit the page in DevTools to make the hidden button visible, click it, and confirm the delete still actually happens.
Add the second permission check immediately before the actual delete logic, and confirm the same bypass attempt no longer works.
Write, in your own words, why a real version of this check would need to come from an actual login system rather than a hardcoded variable.
Hiding a sensitive button or link with CSS or JavaScript and believing that alone prevents the underlying action from happening.
Checking a permission condition only once, when deciding what to display, instead of checking it again immediately before performing the actual action.
Assuming a visitor can only do what the visible page lets them click, rather than what the underlying requests the page sends actually allow.
Believing a simple, no-login CRM like the one built across this course has real permission enforcement, when it genuinely doesn't yet.
Treating a security gap as fixed because the obvious symptom — a visible button — was hidden, without checking whether the underlying action is actually still possible.
Explain specifically why hiding an element with CSS or JavaScript does not prevent the underlying action, identify the missing server-side check in a real example, and describe what real permission enforcement would require that this simple CRM doesn't yet have.
Login - Jit4All
Login
Welcome back to Jit4All