Drupal: Users, Roles and Permissions
|
This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production. This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages. |
Every request to a Drupal site is made by a user, and everything that user may do is decided by permissions that are granted to roles. This page explains the whole chain from the site builder’s point of view: the user account entity, the People screens and registration settings, roles and the permissions screen, the Administrator role, the special status of user 1 and the switch that removes it, the Access Policy API that computes permissions, the node access (grants) system and its "Rebuild permissions" step, the 11.3 permission that controls the published status of content, password policy, the CSRF-protected logout route and the four account cancellation methods. Writing permission checks in module code is covered in Access control and permissions in code; hardening a whole site is covered in Security hardening.
It was written against Drupal 11.4.x, with Drush 13 or 14 and PHP 8.3 or later, and the facts were checked against the
official documentation on 2026-10-30. The running example is the Meridian Museum: the roles Administrator, Content
Editor, Curator, Reviewer and Member, the content types exhibition and event, and the custom entity
collection_object of the module meridian_collection. Drupal 12.0.0 is scheduled for the week of 2026-12-07 and the
end of life of Drupal 10 for 2026-12-09, so both are described as upcoming.
Who uses this and how the pieces fit
Four kinds of people meet the access system:
-
editors see only what their roles allow, and notice it when a menu item or a button is missing;
-
site builders create roles, tick permissions, configure registration and decide how content is protected;
-
administrators create accounts, block or cancel them, and protect user 1;
-
developers declare permissions, write access policies and node grants (see Access control and permissions in code).
The User Guide (chapter 7, "Managing User Accounts") builds its model on three concepts: users, permissions and roles. Every visitor is a user. Visitors who are not logged in are anonymous users, logged-in visitors are authenticated users, and the account created during installation is user 1. A permission names one action or a small group of related actions, such as "View published content" or "Administer users", and is declared by the module that provides the action. You never give permissions to a person directly: permissions go to roles, and roles go to people. Every account automatically has the Authenticated user role, and any number of further roles can be added.
The permissions of the current user are not read from the roles at the moment of each check. Since Drupal 10.3 they are
calculated by access policies (described below), cached under the user.permissions cache context, and then looked up
by $account->hasPermission(). For a site builder the visible part is still the role and permission screens.
User accounts
A user account is a content entity of type user (class Drupal\user\Entity\User). Its base fields include uid,
uuid, langcode, preferred_langcode, preferred_admin_langcode, name (the username), pass (the password hash),
mail, timezone, status (active or blocked), created, changed, access (last access), login (last login),
init (the email address used at registration), roles and default_langcode. The user picture is a normal field
added by the Standard profile, and you can add your own fields at Configuration > People > Account settings > Manage
fields (admin/config/people/accounts/fields), exactly as you would for a content type (see
Content types and fields). The Meridian site could, for instance, add a
field_membership_number text field for members.
The User Guide describes creating an account at Manage > People (admin/people) with the Add user button
(section 7.4, "Creating a User Account"). The form asks for:
-
Email address: used for system emails and not shown publicly;
-
Username: used to sign in and to author content; spaces are allowed, and the only punctuation accepted is periods, hyphens, apostrophes and underscores;
-
Password and Confirm password: with a strength gauge;
-
Status: Active or Blocked, and blocked users cannot sign in;
-
Roles: the additional roles for the account;
-
Notify user of new account: sends the "Welcome (new user created by administrator)" email;
-
Picture and Contact settings: the personal contact form switch, which only has an effect when the Contact module is installed.
The People page (admin/people) is a View (views.view.user_admin_people). It filters by name or email, role,
permission and status, and offers bulk actions to add or remove a role, block or unblock, and cancel the selected
accounts. Because it is a View, a site builder can add columns or filters to it like to any other listing (see
Views).
Accounts can be managed from the command line with Drush, which is convenient in provisioning scripts and in the DDEV environment (see Drush). Passwords here come from the shell, never from a file in the repository.
# Create a curator account; the password is read from an environment variable
ddev drush user:create amara.okafor \
--mail="amara.okafor@meridian-museum.example" \
--password="$MERIDIAN_CURATOR_PASSWORD"
ddev drush user:role:add curator amara.okafor
# One-time login link for an administrator who is locked out
ddev drush user:login --name=amara.okafor
# Information, blocking, unblocking and a role listing
ddev drush user:information amara.okafor
ddev drush user:block amara.okafor
ddev drush user:unblock amara.okafor
ddev drush role:list --format=yaml
Source: User Guide: Creating a User Account and Drush 13 commands
Registration settings
Configuration > People > Account settings (admin/config/people/accounts, permission Administer account settings)
holds the settings stored in user.settings. The ones a site builder meets first are the registration options under
Who can register accounts?:
| Option | Configuration value (register) |
Meaning |
|---|---|---|
Administrators only |
|
Visitors cannot create accounts; staff create them at |
Visitors |
|
Anyone can register, and the account is active immediately (after email verification when it is required). |
Visitors, but administrator approval is required |
|
Anyone can register, but the account stays blocked until an administrator activates it, and the visitor is told so. |
Next to it you find Require email verification when a visitor creates an account (verify_mail), the default
cancellation method (cancel_method, see below), and the editable email templates: "Welcome (new user created by
administrator)", "Welcome (awaiting approval)", "Welcome (no approval required)", "Account activation", "Account
blocked", "Account cancellation confirmation", "Account canceled" and "Password recovery". The [site:...] and
[user:...] tokens are available in them. The same screen lets you set the Anonymous users name and the picture
settings.
Default since 11.1. In new sites built with the Standard or Minimal installation profile, the default of Who can register accounts? is Administrators only. Earlier releases defaulted to visitors with administrator approval, which on public sites produced floods of spam accounts. The change record applies only to new sites: a site installed before Drupal 11.1 keeps whatever it has stored, so check the setting of every older site. Tutorials and books that tell you to "leave the default registration alone" for a members' area predate this change; on a fresh site you must change the option yourself before visitors can sign up.
For the Meridian Museum the choice is deliberate. Staff accounts are created by an administrator; Members (logged-in visitors who get a membership card page) register themselves with email verification, so the site builder sets the option to Visitors, keeps Require email verification on, and relies on a captcha or anti-spam module (see Forms for visitors: Contact and Webform) rather than approving each account by hand. The settings can be exported like any other configuration:
# config/sync/user.settings.yml (excerpt)
register: visitors
verify_mail: true
cancel_method: user_cancel_block
notify:
status_activated: true
status_blocked: false
status_canceled: false
password_reset_timeout: 86400
Common pitfalls with registration:
-
Opening registration and giving the Authenticated user role permissions that were meant for staff. Everything an authenticated user can do, every registered spammer can do.
-
Turning off email verification on a public site, which lets anyone create accounts with other people’s addresses.
-
Forgetting that password reset links are valid for
password_reset_timeoutseconds (one day by default) and that the one-time login link produced bydrush user:loginfollows the same rule. -
Expecting the registration option to affect accounts created by JSON:API, migrations or code: those paths use the entity API directly and are protected by permissions, not by this setting.
Roles
A role is a configuration entity of type user_role (class Drupal\user\Entity\Role) exported as
user.role.<id>.yml. Its properties are id, label, weight (the order in listings), is_admin and permissions.
Two roles always exist: Anonymous user (anonymous) and Authenticated user (authenticated). They cannot be
deleted or assigned to accounts by hand. A request is made either with the anonymous role or with the authenticated role
plus every role assigned to the account. The Standard profile also creates Content editor and Administrator.
Roles are created at People > Roles (admin/people/roles) with Add role, and require the Administer permissions
permission (section 7.3 of the User Guide, "Creating a Role"). The machine name is generated from the label and cannot
be changed afterwards. Roles are reordered by drag and drop; the weight only affects listings and the order of the
columns in the permissions screen, not what the role may do.
The Meridian roles, exported from the configuration screen, look like this:
# config/sync/user.role.curator.yml
langcode: en
status: true
dependencies:
config:
- node.type.exhibition
module:
- meridian_collection
- node
id: curator
label: Curator
weight: 4
is_admin: false
permissions:
- 'access content'
- 'access content overview'
- 'create exhibition content'
- 'edit any exhibition content'
- 'edit own event content'
- 'view own unpublished content'
- 'administer node published status'
- 'manage collection objects'
Source: User Guide: Creating a Role
Notice the dependencies block. Permissions create configuration dependencies: a role that grants
create exhibition content depends on the module that declares the permission and on the configuration that the
permission is generated from (here the content type). If a module is uninstalled or a content type is deleted, Drupal
removes the permission from the role instead of leaving an orphan. When you export and import configuration between
environments, the dependency order makes sure the content type exists before the role refers to it.
Assigning roles. A role is given to one account at its edit form, in the Roles section, or to many accounts at once from the People page by choosing a bulk action such as "Add the Curator role to the selected user(s)" and pressing Apply to selected items (User Guide section 7.6, "Changing a User’s Roles"). The permission Administer permissions (and Administer users for editing other accounts) is needed for this; both are marked as having security implications. The role assignment takes effect on the next request of that user, because the permission cache context depends on the roles.
Permissions
Permissions are declared by modules in MODULE.permissions.yml (static ones) or by a callback listed there under
permission_callbacks (dynamic ones, such as "Create exhibition content" for every content type). The screens are:
-
People > Permissions (
admin/people/permissions): one grid with every permission, grouped by module, and one column per role. On a site with many modules, the per-module page (admin/people/permissions/module/node) and the per-role page (admin/people/permissions/curator) are easier to use. -
People > Roles > Edit permissions for one role, which is the form used by User Guide section 7.5, "Assigning Permissions to a Role": tick the boxes and press Save permissions.
A permission can carry restrict access: true. The screen then prints "Warning: Give to trusted roles only; this
permission has security implications." Examples are Administer permissions, Administer users, Administer modules
and Bypass content access control. A role that holds a restricted permission is as trustworthy as the people who hold
that role. Another warning, specific to input formats, deserves attention: the permission to use a text format with
unrestricted HTML (see Text formats and CKEditor 5) is also a way to
run JavaScript in other users' browsers.
Useful permissions for the Meridian site:
| Permission | Effect |
|---|---|
|
View published content; required by almost everything on the front end. |
|
Lets the author of a draft see it without any wider right. |
|
Per-bundle content permissions, generated for every content type. |
|
Broad administration of content, including the status, authoring and promotion options. Treat as an administrator right. |
|
Ignores all node access checks (hooks and grants). |
|
Added in 11.3: changes the Published checkbox without the rest of |
|
Restricted: can grant any permission, or edit any account. |
|
Opens |
|
Lets users cancel their own account, and choose how. |
|
Lets users rename themselves; off by default for authenticated users. |
|
Admin interface access for staff roles. |
Anonymous and authenticated, in practice. Because every account holds the Authenticated user role, a permission granted
to it is granted to all accounts, including Members. The anonymous role is a separate set: a permission given to
authenticated users is not given to anonymous visitors. The permissions grid therefore needs a sanity check after each
module installation, and the Status report (admin/reports/status) as well as the Security Review contrib module can
point at the riskiest grants. Recipes for common shapes are covered in Recipes.
Permissions are configuration, so they can also be granted without clicking. A recipe such as meridian_events can
grant permissions to roles through config actions (see Recipes for the full format):
# recipes/meridian_events/recipe.yml (excerpt)
name: 'Meridian events'
type: 'Content type'
install:
- node
- user
config:
import:
node:
- node.type.event
actions:
user.role.content_editor:
grantPermissions:
- 'create event content'
- 'edit own event content'
- 'delete own event content'
user.role.curator:
grantPermissions:
- 'edit any event content'
Source: Drupal recipes
And from the command line, for ad hoc work or provisioning:
ddev drush role:create reviewer "Reviewer"
ddev drush role:perm:add reviewer "access content,view own unpublished content"
ddev drush role:perm:remove authenticated "change own username"
ddev drush role:list --filter=reviewer
ddev drush config:export -y # then commit config/sync/user.role.*.yml
Source: Drush 13 commands
The Administrator role
The Administrator role (administrator) of the Standard profile is a role whose is_admin flag is true. A role with
this flag has all permissions, including the ones that modules add later, and its permission list is not even shown on
the permissions page. That is why a new module’s permissions never need to be granted to administrators.
The flag can be turned on or off for any role, in the role’s edit form ("This role is an administrator role"), or by the
is_admin key in configuration. Two practical consequences follow. First, any account that gets such a role has full
control of the site, so the right to assign roles (Administer permissions and Administer users) is itself equivalent
to administrator access. Second, because administrator roles have no permission list, you cannot make an administrator
"almost" an administrator: split a limited operations role off, with explicit permissions, and keep is_admin for the
few people who really need it.
In Drupal 12 the same model continues; the move of the Toolbar module to contrib and the removal of Claro mean that the
admin interface a role sees changes, but not the is_admin mechanism. Check the upgrade page of your release
(Preparing for Drupal 12).
User 1 and the super-user access policy
The account created by the installer has user ID 1 (uid 1). The User Guide (section 7.2, "Concept: The User 1
Account") calls it the root account: it can do everything regardless of its roles, including editing all content and all
accounts, changing configuration, installing modules and running updates. It cannot be deleted from the admin interface,
and removing it from the database is strongly discouraged. The guide recommends that every administrator has a separate
account with the Administrator role and that user 1 is used as little as possible: separate accounts make actions
traceable, allow restrictions per person, and make clear who authored what.
Drupal 10.3 and later: the super-user policy. Before 10.3 user 1 was hard-coded as an exception inside the access
checks. Since the Access Policy API was added in 10.3, user 1’s privileges are provided by one core policy, the
SuperUserAccessPolicy, which can be switched off. The switch is a container parameter in the site’s services file
(sites/default/services.yml, copied from default.services.yml):
# web/sites/default/services.yml
parameters:
# Treat user 1 like any other account: it only has what its roles grant.
security.enable_super_user: false
Points to know before using it:
-
The container must be rebuilt after the change (clear caches from the UI, with
drush cache:rebuildor withcore/rebuild.php). -
The change record recommends turning the policy off whenever the site has at least one other administrator. A stolen or guessed user 1 login is then no longer automatically a full compromise, and user 1 becomes an ordinary account.
-
The parameter has an effect only when user 1 does not also hold the Administrator role (a role with
is_admin). An account that has both continues to be an administrator through the role. -
Lockout recovery: set the parameter back to
trueand rebuild the container, and user 1 is a super user again. -
Core’s own Browser, Kernel and Nightwatch tests run with the policy off. Tests of contributed modules keep it on unless the test class sets
$usesSuperUserAccessPolicytoFALSE; test users must be given the permissions they need. -
The change record states that the long-term aim is to remove the super-user policy entirely.
A sensible hardening recipe for Meridian: create named administrator accounts with the Administrator role, put the user
1 credentials in the password manager, set security.enable_super_user: false in the production services file (and keep
it true locally if that is convenient), and use drush user:login from the command line, which can print a one-time
login link for any account, when you are locked out.
Bad advice that older books and blog posts still contain: "log in as user 1 for everything", "give the editor the user 1
account", and "uid 1 bypasses everything, so test with it". Test with a real role instead, and remember that user 1 also
bypasses node access, field access and routes with _permission requirements, so a feature that works for user 1 may
fail for everybody else.
The Access Policy API from a site builder’s view
Since Drupal 10.3, permissions are computed by access policies. A policy is a service tagged access_policy that,
for a given account and scope, returns a set of permissions. The calculation happens once per account and is cached
with cache contexts, so that $account->hasPermission() is a cheap lookup. The documentation describes the idea as
granting or revoking permissions based on an environmental variable such as the logged-in user, the time of day or the
active URL.
Core ships these policies:
-
UserRolesAccessPolicy: reads the roles of the account and adds the permissions from every role. For roles flaggedis_adminit grants admin access (all permissions). This is the policy behind everything in the previous sections. -
SuperUserAccessPolicy: gives user 1 admin access unlesssecurity.enable_super_userisfalse.
What a site builder needs to know:
-
The role-and-permission screens are still where you work; policies do not replace them.
-
Contributed modules may add policies. A module that says "gives curators extra rights during a loan season" or "restricts editors to one domain" is probably doing it through a policy. Such permissions may not appear in the permission grid of a role, because they are computed, not stored. If someone has a permission that no role grants, look for a policy in
*.services.yml(a service taggedaccess_policy) or in the module’s documentation. -
Scopes: policies work within a scope and an identifier. The default scope is
drupal, which core uses. Contributed modules can define other scopes, for example one per group or per domain. Core does not use other scopes. -
The calculated permissions are immutable for the rest of the request: code cannot add a permission in the middle of a request. The API has a build phase (
calculatePermissions()) and an alter phase (alterPermissions()). -
Cache correctness is the responsibility of the policy: whatever the policy depends on must be declared through cache contexts (
getPersistentCacheContexts()) or cacheable metadata, or users could see stale permissions.
For the Meridian Museum the Curator extra right is not a role right but an access policy, shown here from the module
side so the site builder can recognise it. During the annual loan season (a flag in meridian_collection.settings)
curators additionally get the permission manage object loans:
<?php
declare(strict_types=1);
namespace Drupal\meridian_collection\Access;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Session\AccessPolicyBase;
use Drupal\Core\Session\CalculatedPermissionsItem;
use Drupal\Core\Session\RefinableCalculatedPermissionsInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Grants curators the loan-management permission during the loan season.
*/
final class LoanSeasonAccessPolicy extends AccessPolicyBase {
public function __construct(private readonly ConfigFactoryInterface $configFactory) {}
public function calculatePermissions(AccountInterface $account, string $scope): RefinableCalculatedPermissionsInterface {
$calculated = parent::calculatePermissions($account, $scope);
// The result depends on the roles of the account and on the setting.
$calculated->addCacheContexts(['user.roles']);
$settings = $this->configFactory->get('meridian_collection.settings');
$calculated->addCacheableDependency($settings);
if (!in_array('curator', $account->getRoles(), TRUE) || !$settings->get('loan_season')) {
return $calculated;
}
return $calculated->addItem(new CalculatedPermissionsItem(['manage object loans']));
}
public function getPersistentCacheContexts(): array {
return ['user.roles'];
}
}
# meridian_collection.services.yml
services:
meridian_collection.loan_season_access_policy:
class: Drupal\meridian_collection\Access\LoanSeasonAccessPolicy
arguments: ['@config.factory']
tags:
- { name: access_policy }
Source: Access Policy API
The documentation advises marking policy classes final and not extending another module’s policy, because policies
take part in both the build and the alter phase; decorate a policy instead. Keep the condition in a dedicated service
if both a cache context and a policy need it. The policy above declares manage object loans in the module’s
meridian_collection.permissions.yml, like any other permission, so it also appears in documentation and can be granted
through roles when the loan season is not the criterion.
Common errors: forgetting the cache context (permissions then stay as computed for the first user of a cache entry);
using \Drupal::currentUser() instead of the account passed to the method; and writing a policy for something that a
role permission already expresses.
Node access and "Rebuild permissions"
Permissions such as edit any exhibition content are checked first, but content has a second, finer layer: the node
access system. It decides who may view, update or delete an individual node.
For a node operation Drupal asks, in this order of importance:
-
Is the user allowed by
bypass node access, or is it an administrator? Then yes. -
Does a module’s
hook_node_access()(or a node access control handler decision) say yes or no? -
If node grants are in use, does a grant in the
node_accesstable match the grants the user holds? -
Otherwise, the role permissions for the bundle (
edit own exhibition content, …) and the published status apply.
The database part works as follows. A module implementing hook_node_access_records() writes records when a node is
saved: a record has a realm (a namespace chosen by the module), a gid (a grant ID, an integer meaning whatever the
module wants) and the flags grant_view, grant_update and grant_delete. On access the module’s hook_node_grants()
tells core which gid values in each realm the user holds, for the operation being checked. If a record and a held
grant match, access is given. Denials are implicit: a deny-all record is not stored. Core writes one default record with
node ID 0 and realm all when no module uses grants. The node access system does not distinguish published from
unpublished nodes by itself, so a module that cares has to use its realms to restrict access to unpublished content.
Listings are also affected: queries tagged node_access (Views, entity queries with accessCheck(TRUE), the search
index) join the node_access table so users do not see rows they may not open. That is why entity queries must always
call accessCheck() explicitly.
Records are written when a node is saved. If a module that provides grants is installed, changed, or has its rules
altered, the existing nodes still carry the old records. Then Drupal shows "The content access permissions need to be
rebuilt." The fix is Reports > Status report > Rebuild permissions (admin/reports/status/rebuild), which runs a batch
over all nodes and calls the hooks again, or in code and scripts:
ddev drush php:eval '\Drupal::service(\Drupal\node\NodeAccessRebuild::class)->rebuild();'
ddev drush cache:rebuild
Source: hook_node_grants()
What to remember about the rebuild:
-
It can take a long time on big sites and rewrites the
node_accesstable, so run it during a quiet period, and not on every deployment: only after the access rules changed. -
The rebuild erases the node ID 0 ("view all") records, so a module that relies on such a record has to restore it after a rebuild, as the API documentation of
hook_node_grants()warns. -
A site with no node-access module uses only role permissions; the rebuild is then trivial. Modules such as Content Access, Group, Domain Access or Workspaces-related setups bring grants.
-
Content moderation does not use grants for its states; it uses permissions such as "Use the Draft to Review transition", see Content moderation, workflows and revisions.
In the Meridian example, curators from a partner museum may only see the objects marked for their loan program. That is
implemented by a small module with grants in a realm meridian_loan; after enabling it, the first action of the site
builder is Rebuild permissions.
The 11.3 "Administer node published status" permission
Until 11.3 the only way to let a person publish and unpublish content was administer nodes, which also gives access to
authoring information, promotion settings, the revision log and many other options. Drupal 11.3 adds the permission
administer node published status (shown as Administer node published status) so that editors can change the
Published state of a node of any content type without the broad permission. The access check for the status field
accepts either permission. administer nodes remains and still includes the capability.
For the Meridian Museum this separates two jobs: Content Editors write and save drafts, while Curators and Reviewers
publish. Grant the new permission to the roles that publish, and remove administer nodes from them:
ddev drush role:perm:add curator "administer node published status"
ddev drush role:perm:remove curator "administer nodes"
ddev drush cr
Notes and pitfalls:
-
The permission governs the published checkbox of the node edit form. When Content Moderation controls a content type the Published checkbox is replaced by moderation states, and the transition permissions apply instead.
-
Contributed permission modules that offered an equivalent "administer node status" permission are being updated to use the core permission (one such module plans an update function that swaps its permission for the core one).
-
Roles that depended on
administer nodesjust to publish should be reviewed after upgrading to 11.3, then the broader permission can be removed from them.
Password policy
Core enforces a very small password rule set: the password must be non-empty and the form shows a strength gauge, which
is advice only. Hashing uses PHP’s password_hash() through the password service, and stored hashes are upgraded when
the user next logs in; Drupal 12 defaults to the argon2id algorithm (which requires PHP with argon2 support).
Organisations that need complexity, history or expiry rules use the contributed Password Policy module
(drupal/password_policy, https://www.drupal.org/project/password_policy). Its constraints cover character types, delay,
digits, history, length, letters, punctuation, upper and lower case and not using the username, and policies can force
expiry and a reset for chosen roles. At the time of writing the stable 4.0.x release (4.0.3) declares compatibility with
Drupal 9.1, 10 and 11, and the project is marked as minimally maintained, so check the release list for your Drupal
version before relying on it, and check the Drupal 12 status page when planning that upgrade. Its limitation matters for
audits: policies apply to passwords set through user forms in the web interface, so Drush, web services or migrations can
bypass them.
Combine policy rules with multi-factor authentication or single sign-on where the project requires it (see
Authentication and OAuth), and with flood control, which core has: the
user.flood configuration limits failed logins per IP address and per user (by default five failed attempts per user
within six hours and fifty per IP address per hour).
Source: Password Policy project page
Logout and CSRF protection (10.3)
Before Drupal 10.3 anyone could log a visitor out by embedding /user/logout in an image or a link on another site. Since
10.3 the route user.logout requires a CSRF token. A request without a valid token is redirected to a confirmation form
(route user.logout.confirm) that asks the user whether to log out, so an attacker’s link cannot silently end a session.
The change breaks hard-coded links. In templates, menus and blocks always generate the URL from the route so that the token is added:
{# Wrong: shows the confirmation form to every visitor #}
<a href="/user/logout">{{ 'Log out'|t }}</a>
{# Right #}
<a href="{{ path('user.logout') }}">{{ 'Log out'|t }}</a>
Practical notes: the standard User account menu already contains the correct link. A custom menu link that points to
/user/logout as an external path (a common habit in older tutorials and books written for Drupal 8 and 9) has to be
replaced by a link to the route user.logout. Decoupled front ends that call the logout path directly, as in the
Next.js kiosk of the Meridian Museum, need the token from the logout_token returned at login, or they should use their
token-based authentication flow (see Authentication and OAuth).
Account cancellation methods
An account can be cancelled by an administrator (from the People page, the account’s Cancel account tab or
drush user:cancel) or by the owner, if the role has the cancel account permission. The form offers four methods,
whose labels and machine names are:
| Label | Method | What happens |
|---|---|---|
Disable the account and keep its content. |
|
The account is blocked; its content stays published under its name. This is the default. |
Disable the account and unpublish its content. |
|
The account is blocked and its nodes and comments are unpublished. |
Delete the account and make its content belong to the Anonymous user. |
|
The account is deleted and its content is reassigned to the anonymous user. This cannot be undone. |
Delete the account and its content. |
|
The account and all of its content are deleted. This cannot be undone. |
The default (cancel_method in user.settings) is chosen on the Account settings screen. The method choice appears for
users who hold select account cancellation method; other users get the default. Removing accounts is done in a batch,
so the process works for people with a lot of content. The options "Require email confirmation to cancel account" and
the notification emails are also on the Account settings screen.
Developers can react to cancellations with hook_user_cancel() for the three non-deleting methods, but the hook is not
invoked for user_cancel_delete: put cleanup of the delete case in hook_user_delete(). A method that defines an
access check cannot be configured as the default method.
For the Meridian Museum the default is user_cancel_block: curators who leave keep their authorship on exhibitions,
which is needed for the museum’s records. Members who ask for erasure under data protection rules get
user_cancel_reassign, after the Privacy Officer has checked the content. Never use user_cancel_delete on staff
accounts that authored published exhibitions without an editorial review, because it removes their content, too. Note
that user 1 cannot be cancelled from the interface, only blocked.
ddev drush user:cancel --delete-content amara.okafor # user_cancel_delete
ddev drush user:cancel amara.okafor # uses user_cancel_block by default
Source: user_cancel_methods()
Common mistakes and a review checklist
-
Everything on the Authenticated role. Grant permissions to specific roles; keep Authenticated for the minimum, since it includes every self-registered account.
-
Sharing user 1. Use named accounts with the Administrator role and consider
security.enable_super_user: false. -
Granting
administer permissionsoradminister usersto non-administrators. These two are equivalent to full control. -
Assuming the grid shows everything. Access policies and node grants can add access that is not visible there.
-
Forgetting to rebuild node access after enabling or changing a grants module.
-
Missing
accessCheck()in entity queries written for listings, which then ignores node grants. -
Hard-coded
/user/logoutlinks in menus and themes since 10.3. -
Treating registration defaults as stable. Verify
registeron every site, and never rely on the 11.1 default for sites that were installed before it. -
Exporting configuration without reviewing
user.role.*.ymldiffs. A permission change is a security change; review the exported file in the pull request, as described in Configuration management.
Where older books and tutorials are outdated
Older books such as Drupal 10 Masterclass (2023) and Drupal 10 Development Cookbook (2023) describe the user and role
screens correctly in outline, but pre-date several changes described above. The Standard profile in 11.4 no longer
creates the Article and Page content types, so permission examples such as "Article: Create new content" refer to
content types that you must create first. Registration used to default to visitors with administrator approval and now
defaults to administrators only for new sites (11.1). User 1 used to be hard-wired and cannot be switched off before 10.3,
whereas it can now be switched off with security.enable_super_user. The /user/logout route is CSRF protected since
10.3, and the Access Policy API (10.3) is not covered by the books at all. The permission to publish content on its own
(11.3) did not exist. Finally, these books show checks written with \Drupal::currentUser() inside classes, where current
code injects the account or current_user service instead.