# User Groups and Permissions ## in Joomla **Who you are · what you can see · what you can do**
**Peter Martin —
db8 Website Support
/
petermartin.nl
**
Note: The whole user system boils down to three questions. Keep those three in mind for the next hour. -- ## Overview 1. The basics 2. The user account 3. User groups 4. Viewing access levels 5. Permissions (ACL) 6. The big picture 7. MFA & security 8. Under the hood 9. Pitfalls & best practices Note: We start gentle and conceptual, then go deeper into ACL, and finish with developer/database material. --- ## 1 # The Basics *Why this matters, and the three pillars* Note: Set the scene before any clicking. The mental model first. -- ### Why users matter - No users → everyone sees the same site - No admin protection - No members area - Security incidents follow Note: Without user management there's no separation between editors and visitors. With it: public, members-only and staff content side by side, self-service registration, MFA. -- ### The three pillars - **Groups** — who are you? - **Access levels** — what may you see? - **Permissions** — what may you do? Note: Three independent building blocks. Groups live under Users → Groups, access levels under Users → Access Levels, permissions in Global Configuration and everywhere else. The whole talk is how these combine. -- ### Where to find it ``` Users → Manage (accounts) Users → Groups (groups) Users → Access Levels (viewing access) Users → Fields (profile fields) Users → User Notes (internal notes) ``` Note: Permissions are the exception — they live in Global Configuration's Permissions tab, plus each component, category and item. --- ## 2 # The User Account *One account, one database row* Note: Now the concrete object: a single user. -- ### What is a user? - One login (front, back, or both) - One row in `#__users` - Linked to one or more groups - No group → can't log in anywhere Note: Unique id, username, email. A user without a group is valid but useless — they can't log in. -- ### Account fields - Name · Username · Email - Password — bcrypt, never plain text - Block · Receive system emails - Require password reset Note: Username is case-sensitive in Joomla 6. Email must be unique. Registered/last visit/last reset timestamps are maintained automatically. -- ### Creating a user - **Backend** — Users → Manage → New - **Frontend** — self-registration view Note: Backend: admin picks group and password, email optional. Frontend self-registrants land in Registered. Activation flow (none / self / admin) is set in Global Configuration → Users. -- ### Switching a user off - **Block** — login refused, account stays - **Delete** — row removed Note: Block for temporary suspension or someone who may return. Delete for never-used accounts or GDPR erasure. Important: deleting does NOT delete their content — created_by points to nothing. Reassign authorship first. -- ### User Notes — the forgotten feature - Internal notes per user - Categorised (uses com_categories) - Invisible to the user Note: Great audit trail for support tickets, membership history, moderation. Most site owners never discover it. --- ## 3 # User Groups *The single most important ACL concept* Note: Get groups right and the rest falls into place. -- ### What is a group? - A labelled bucket of users - Carries **no** permissions by itself - User → **multiple** groups - Groups **nest**; children **inherit** Note: Permissions are assigned to the group later, through the ACL. A child inherits the parent's permissions unless you override. -- ### The default group tree ``` Public ├── Guest ├── Manager │ └── Administrator ├── Registered │ └── Author │ └── Editor │ └── Publisher └── Super Users ``` Note: Public = everyone. Guest = explicitly not logged in. Registered = any logged-in user. Author/Editor/Publisher = rising frontend rights. Manager/Administrator = backend. Super Users = anything anywhere. -- ### Inheritance in practice - Inherits **permissions**, not **users** - Editor ≠ also in Author - But Editor *can* do what Author can - Deny on Editor breaks the chain Note: Common confusion. User assignments are leaf assignments only; permission inheritance flows down the tree. -- ### Creating a custom group - Group Title - Group Parent — pick the closest match Note: Parent decides your starting permissions. Closest match = fewer overrides. Real examples: Members (parent Registered), Moderators (parent Editor), Site Admins (parent Administrator). -- ### Many-to-many in action - A user can sit in several groups - Gets the **union** of permissions - Allow wins... unless a Deny exists Note: Jane is a Newsroom Editor AND a Forum Moderator. Tick both groups. "Deny always wins" is what makes combining groups safe. --- ## 4 # Viewing Access Levels *What may you see?* Note: We move from identity to visibility. -- ### What is an access level? - "Which groups may **see** this?" - A title + a list of groups - Assigned to articles, modules, menus... Note: Group says who you are. Access level says who is allowed to see this content. Subtle but important difference. -- ### The default access levels | Level | Granted to | |-------|-----------| | Public | everyone | | Guest | not logged in | | Registered | logged-in members | | Special | Author and above | | Super Users | Super Users only | Note: These ship out of the box. Special = editors, staff, admins. Super Users for sensitive admin-only modules. -- ### Custom access level ``` Title: Premium Content Groups: [x] Premium Members [x] Super Users (or you lock yourself out) ``` Note: Classic need: premium members read the full article, everyone else the teaser. Always include Super Users. -- ### The Guest trick - Granted to Guest group only - Visible to anonymous visitors only Note: Uses: a Login module that disappears once logged in, a newsletter banner for anonymous visitors, teaser swapped for full content after login. Combine a Guest and a Registered module on the same position. -- ### Where access levels apply - Articles, categories, featured - Menu items - Modules - Contacts, newsfeeds, banners - Custom fields Note: Same dropdown, same effect, everywhere. It gates visibility. --- ## 5 # Permissions (ACL) *What may you do?* Note: The deepest part. From visibility to action. Take it slow here. -- ### From visibility to action - May they create articles? - Edit own, or any? - Publish? Delete in a category? - Log in to the backend at all? Note: Access levels = who can see. Permissions = who can do. -- ### The standard action set - Site / Administrator Login - Super User · Configure - Access Admin Interface - Create · Delete · Edit · Edit State - Edit Own · Edit Custom Field Value Note: Most components share this set; some add their own. Edit = any item; Edit Own = only your own; Edit State = publish/unpublish/archive/trash. -- ### The four permission states | State | Effect | |-------|--------| | Inherited | take the parent's value (default) | | Allowed | explicitly grant | | Denied | forbid + lock below | | Not Set | top only, treated as Deny | Note: After saving, Joomla shows the calculated result: Allowed, Not Allowed, or the dreaded Not Allowed (Locked). -- ### The permission hierarchy ``` Global Configuration └── Component Options (e.g. com_content) └── Category permissions └── Item permissions (per article) ``` Note: Baseline at Global. Tighten per component. Override per category for "staff only" sections. Item level only as a last resort. -- ### The Deny trap - **Deny always wins** - No Allow lower down can rescue it - Lower levels show *Not Allowed (Locked)* Note: Guidelines: default to Inherited; use Allowed to grant downward; use Denied only when the rule must be permanent and absolute, usually at Global. Lost a permission? Walk UP the tree looking for a Deny. -- ### Global Configuration baseline | Group | Site | Admin | Super | |-------|------|-------|-------| | Public | — | — | — | | Registered | Allow | — | — | | Author/Editor/Publisher | Allow | — | — | | Manager / Administrator | Allow | Allow | — | | Super Users | Allow | Allow | Allow | Note: System → Global Configuration → Permissions. Get this layer right and ~80% of ACL "weirdness" disappears. -- ### Component & category level - **Component** — day-to-day ACL lives here - **Category** — editorial workflows - **Item** — sparingly, last resort Note: Component: tighten Create, grant Edit State to Publishers only, give a Newsroom group admin access without Manager. Category: separate newsroom/blog/member teams without giving anyone global Edit. Item: a single sensitive article — otherwise make a category. -- ### Worked example: Newsroom Editors 1. Group — parent Registered 2. Global → Site + Admin Login = Allow 3. Articles Options → Access Admin = Allow 4. Press Releases category → Create/Edit/Edit State/Delete = Allow 5. Other categories → inherited Note: Goal: fully manage Press Releases and nothing else. No Super User, no over-privilege. This is the canonical pattern. --- ## 6 # Putting It All Together *The full picture* Note: Step back and connect the four concepts. -- ### The full flow ``` USER → belongs to → GROUP(s) │ visibility through ▼ ACCESS LEVEL → Article / Module / Menu │ actions through ▼ PERMISSIONS (ACL) ├── Global ├── Component ├── Category └── Item ``` Note: A user is in a group; the group unlocks access levels and carries permissions; access levels gate visibility; permissions gate actions. -- ### Frontend vs backend - Read public — defaults - Read after login — Access = Registered - Submit articles — Create + frontend menu - Manage in /administrator — Admin Login + Access Admin Note: Same ACL gates both front and back. Only the permission flags differ. --- ## 7 # MFA & Security *Hardening the login* Note: Joomla 4 introduced native MFA; 6 refines it. -- ### Native MFA methods - **TOTP** — authenticator apps - **WebAuthn** — passkeys / hardware keys - **YubiKey** - **Email OTP** - Fixed code (testing only) Note: Opt-in per user, or enforce per group via the Captive MFA plugin. Backup codes are generated automatically — remind users to store them safely. -- ### The captive login flow - Trapped on setup until MFA satisfied - Cannot navigate away - Enforce policy without code Note: Users → Manage → Options → Multi-factor Authentication. A powerful way to enforce an org security policy. -- ### Useful user plugins - **User - Joomla** — never disable - **User - Profile** — extra fields - **Contact Creator** — auto contact per user - **Authentication** — Joomla / LDAP / Cookie Note: For custom logic, write a User plugin hooking onUserBeforeSave, onUserAfterSave, onUserLogin, onUserLogout. -- ### Custom profile fields - **Custom Fields** — point-and-click, per-field access - **User Profile plugin** — code, validation logic Note: In Joomla 6, Custom Fields are usually the right answer. Reach for the plugin only when you need server-side validation. --- ## 8 # Under the Hood *Developer view — the hardest part* Note: Now the PHP API, database tables and SQL. This is reference material — fine to show in full. -- ### Getting the current user ```php use Joomla\CMS\Factory; $app = Factory::getApplication(); $user = $app->getIdentity(); if ($user->guest) { // not logged in } echo $user->id; echo $user->username; echo $user->name; ``` Note: The old Factory::getUser() still works but is deprecated. Use getIdentity() in new code. -- ### Checking permissions ```php $user = Factory::getApplication()->getIdentity(); $user->authorise('core.edit', 'com_content'); $user->authorise('core.edit', 'com_content.category.7'); $user->authorise('core.edit', 'com_content.article.42'); ``` Note: authorise() is the heart of the ACL in PHP. The asset string walks the hierarchy automatically — you don't check Global/Component/Category/Item yourself. -- ### Checking access levels ```php $viewLevels = $user->getAuthorisedViewLevels(); if (in_array($article->access, $viewLevels, true)) { // allowed to see it } $query->whereIn( $db->quoteName('a.access'), $user->getAuthorisedViewLevels() ); ``` Note: Every core query that returns content uses this array as the IN(...) filter. -- ### Declaring actions: access.xml ```xml
``` Note: A custom component declares its action set here. These actions show up as columns on the Permissions tab automatically. -- ### Useful database tables
| Table | Holds | |-------|-------| | `#__users` | accounts | | `#__usergroups` | group tree (lft/rgt) | | `#__user_usergroup_map` | user↔group map | | `#__viewlevels` | access levels | | `#__assets` | the asset tree + rules | | `#__user_notes` / `#__user_profiles` / `#__user_mfa` | extras |
Note: The asset tree has one row per component, category or item. This is where permissions actually live. -- ### Reading the rules column ```json { "core.edit": { "3": 1, "5": 0 }, "core.edit.state": { "5": 1 }, "core.delete": { "8": 1 } } ``` `1` = Allow · `0` = Deny · missing = Inherit Note: The rules column on #__assets is plain JSON. Here: Allow core.edit for group 3 (Author), Deny for group 5 (Editor → locked), everyone else inherits. Once you can read this, ACL stops being a black box. -- ### Three SQL queries to know ```sql -- Groups a user belongs to SELECT u.username, g.title FROM jos_users u JOIN jos_user_usergroup_map m ON u.id = m.user_id JOIN jos_usergroups g ON g.id = m.group_id; -- Viewing levels SELECT id, title, rules FROM jos_viewlevels; -- Assets with explicit rules SELECT id, name, rules FROM jos_assets WHERE rules != '{}'; ``` Note: Swap jos_ for your real prefix. These three debug almost any user/group/ACL issue directly in the database. --- ## 9 # Pitfalls *The ones that bite* Note: Common mistakes — most come straight from the rules we covered. -- ### Common mistakes - Deny is forever - Group inheritance ≠ user inheritance - Over-using Super User - Forgetting Super Users in a custom level - Mixing up Guest and Public Note: Deny at Global can't be allowed lower. Keep Super Users to one or two trusted people. Public = everyone; Guest = not logged in. -- ### More pitfalls - Deleting a user keeps their content - Public inside a custom access level = public - Skipping rebuild after direct DB edits Note: Reassign authorship before deleting. Ticking Public in a custom level defeats its purpose. After bulk SQL edits to rules, rebuild the asset tree and clear cache or the UI shows stale results. --- ## 10 # Best Practices *A safe workflow* Note: How to keep it sane long-term. -- ### A safe ACL workflow 1. Plan groups on paper — draw the tree 2. Global baseline → save & test 3. Component permissions → save & test 4. Then custom access levels 5. Category permissions for workflows 6. Item level only as last resort 7. Document every custom group & level Note: The A4 rule: if you can't draw your ACL on one sheet of A4, it's too complex — simplify. -- ### Performance tips - Keep groups under ~20 - Keep nesting under 4 levels - Avoid per-article rule overrides - Watch viewing levels in every query Note: Large ACL trees cost performance. If a site feels slow after a permissions refactor, look at the ACL tree first. --- ## 11 # Quick Reference *Where things live · the rules · the code* Note: A cheat-sheet, split across three cards. Everything we covered. -- ### Where things live ``` USER Users → Manage GROUP Users → Groups ACCESS LEVEL Users → Access Levels PERMISSIONS Global Config + Component + Category + Item ``` Note: The four concepts and where you find each in the backend. -- ### The rules ``` GROUPS Multiple per user; child inherits parent DENY Deny always wins — no Allow rescues it INHERIT Default to Inherited everywhere INCLUDE Always add Super Users to custom levels REBUILD After DB changes: rebuild + clear cache ``` Note: The five rules that prevent 80% of ACL pain. -- ### PHP & database ```php // current user Factory::getApplication()->getIdentity(); // can they act? $user->authorise('core.edit', 'com_content.category.7'); // what can they see? $user->getAuthorisedViewLevels(); // rules live in: #__assets.rules // JSON → 1 = Allow, 0 = Deny ``` Note: The developer entry points: identity, authorise, view levels, and the JSON rules column. --- # The mental model - **Users = who you are** -
**Groups = bundled users**
-
**Access levels = what you can see**
-
**Permissions = what you can do**
--- ## Thank you! # Questions? **Peter Martin —
db8 Website Support
** **Sheets:
petermartin.nl
** Note: Get the three pillars right and Joomla works with you instead of against you. Questions?