When to Use
- Creating, altering, suspending, or dropping users
- Creating roles, granting/revoking role membership
- Granting/denying/revoking graph, database, or DBMS privileges
- Inspecting current privileges (
SHOW PRIVILEGES) - Implementing property-level access control (read/write per property)
- Setting up ABAC rules against OIDC claims or native user tags
- Referencing LDAP/SSO auth provider configuration
When NOT to Use
- Writing Cypher queries against application data →
neo4j-cypher-skill - Cluster ops, backups, server config →
neo4j-cli-tools-skill - Driver connection setup →
neo4j-driver-*-skill
MCP Write Gate — MANDATORY
Before executing ANY of the following, show the planned command and wait for explicit confirmation:
CREATE USER/ALTER USER/DROP USERCREATE ROLE/DROP ROLEGRANT/DENY/REVOKE(any privilege)CREATE AUTH RULE/DROP AUTH RULE
Never auto-execute privilege changes. Show exact Cypher, annotate impact, get "yes".
Execution Context
All security Cypher runs against the system database:
cypher// Neo4j auto-routes CREATE/ALTER/SHOW USER|ROLE|PRIVILEGE to system // If using cypher-shell: cypher-shell -d system // If using driver: use database="system"
1. User Management
Create user
cypherCREATE USER alice SET PASSWORD 'secret' CHANGE NOT REQUIRED; // CHANGE REQUIRED (default): forces password change on first login // CHANGE NOT REQUIRED: password valid immediately // SET STATUS ACTIVE (default) | SUSPENDED
Parameterised password (preferred in scripts)
cypherCREATE USER $username SET PASSWORD $password CHANGE NOT REQUIRED;
Alter user
cypherALTER USER alice SET PASSWORD $newPw CHANGE NOT REQUIRED; ALTER USER alice SET STATUS SUSPENDED; // lock account ALTER USER alice SET STATUS ACTIVE; // unlock ALTER USER alice SET HOME DATABASE mydb; // default db on connect ALTER USER alice IF EXISTS SET PASSWORD $pw; // safe if missing
User tags [2026.06+, Enterprise]
Tags are arbitrary strings on the native user object, readable in ABAC rules via abac.native.user_tags().
cypherCREATE USER jake SET PASSWORD $pw SET TAGS 'finance', 'auditor'; ALTER USER jake REMOVE TAG 'auditor' ADD TAG 'on-call'; // REMOVE before ADD before SET ALTER USER jake SET TAGS 'finance', 'auditor'; // replaces all existing tags ALTER USER jake REMOVE ALL TAGS; ALTER USERS jake, alice ADD TAGS 'pii-access'; // bulk tag edit
SET/ADD/REMOVE TAG[S] requires SET USER METADATA; reading tag values requires SHOW USER METADATA:
cypherGRANT SET USER METADATA ON DBMS TO userAdmin; GRANT SHOW USER METADATA ON DBMS TO auditor; GRANT USER METADATA MANAGEMENT ON DBMS TO userManager; // both of the above
Show users
cypherSHOW USERS YIELD user, roles, passwordChangeRequired, suspended, home WHERE suspended = false RETURN user, roles ORDER BY user; // tags column returns null without SHOW USER METADATA [2026.06+] SHOW USERS YIELD user, roles, tags; // runnable CREATE USER commands for the whole DBMS [2026.09] SHOW USERS AS COMMANDS; SHOW USERS WITH AUTH AS COMMANDS; // includes auth provider config + credentials
SHOW USERS WITH AUTH AS COMMANDS exposes credentials. Use only for secured backup/restore handling. Prefer SHOW USERS AS COMMANDS when auth material is not required. Never paste auth-export output into plaintext docs, logs, tickets, or source control.
Drop user
cypherDROP USER alice IF EXISTS;
2. Role Management
Create / drop role
cypherCREATE ROLE analyst; CREATE ROLE analyst IF NOT EXISTS; DROP ROLE analyst IF EXISTS;
Assign / remove roles
cypherGRANT ROLE analyst TO alice; GRANT ROLE analyst, writer TO alice, bob; // bulk REVOKE ROLE analyst FROM alice;
Inspect roles
cypherSHOW ROLES YIELD role, member ORDER BY role; SHOW ROLE analyst PRIVILEGES AS COMMANDS; // returns runnable GRANT commands SHOW POPULATED ROLES YIELD role; // only roles with members // runnable CREATE ROLE commands for the whole DBMS [2026.09] SHOW ROLES AS COMMANDS; SHOW ROLES WITH USERS AS COMMANDS; // adds GRANT ROLE ... TO user SHOW ROLES WITH AUTH RULES AS COMMANDS; // adds GRANT ROLE ... TO AUTH RULE
3. Privilege Decision Table
| Goal | Command |
|---|---|
| Allow db connection | GRANT ACCESS ON DATABASE mydb TO analyst |
| Read all graph data | GRANT MATCH {*} ON GRAPH mydb ELEMENTS * TO analyst |
| Read specific label | GRANT MATCH {*} ON GRAPH mydb NODES Person TO analyst |
| Read specific rel type | GRANT MATCH {*} ON GRAPH mydb RELATIONSHIPS KNOWS TO analyst |
| Read one property | GRANT READ {email} ON GRAPH mydb NODES Person TO analyst |
| Traverse but hide properties | GRANT TRAVERSE ON GRAPH mydb NODES Person TO analyst |
| Write (create/set) | GRANT WRITE ON GRAPH mydb TO writer |
| Create nodes only | GRANT CREATE ON GRAPH mydb NODES Person TO writer |
| Delete nodes only | GRANT DELETE ON GRAPH mydb NODES Person TO writer |
| Execute procedure | GRANT EXECUTE PROCEDURE apoc.* TO analyst |
| Execute function | GRANT EXECUTE USER DEFINED FUNCTION apoc.* TO analyst |
| All on one db | GRANT ALL ON DATABASE mydb TO dba |
| Full DBMS admin | GRANT ALL ON DBMS TO dba |
| Manage users | GRANT USER MANAGEMENT ON DBMS TO secadmin |
| Manage user tags [2026.06] | GRANT USER METADATA MANAGEMENT ON DBMS TO secadmin |
| Manage roles | GRANT ROLE MANAGEMENT ON DBMS TO secadmin |
| Schema changes | GRANT CREATE ELEMENT TYPES ON DATABASE mydb TO schemaadmin |
DENY overrides GRANT
cypher// Analyst can read Person but NOT the ssn property GRANT MATCH {*} ON GRAPH mydb NODES Person TO analyst; DENY READ {ssn} ON GRAPH mydb NODES Person TO analyst;
REVOKE removes a specific grant or deny
cypherREVOKE GRANT READ {email} ON GRAPH mydb NODES Person FROM analyst; REVOKE DENY READ {ssn} ON GRAPH mydb NODES Person FROM analyst; REVOKE MATCH {*} ON GRAPH mydb NODES Person FROM analyst; // removes both grant+deny
4. Common Role Patterns
Read-only analyst
cypherCREATE ROLE analyst IF NOT EXISTS; GRANT ACCESS ON DATABASE mydb TO analyst; GRANT MATCH {*} ON GRAPH mydb ELEMENTS * TO analyst; GRANT EXECUTE PROCEDURE apoc.* TO analyst;
Write role (no admin)
cypherCREATE ROLE writer IF NOT EXISTS; GRANT ACCESS ON DATABASE mydb TO writer; GRANT MATCH {*} ON GRAPH mydb ELEMENTS * TO writer; GRANT WRITE ON GRAPH mydb TO writer;
Read-only on specific labels only
cypherCREATE ROLE limited_reader IF NOT EXISTS; GRANT ACCESS ON DATABASE mydb TO limited_reader; GRANT TRAVERSE ON GRAPH mydb ELEMENTS * TO limited_reader; // can traverse GRANT MATCH {*} ON GRAPH mydb NODES Person TO limited_reader; // Person props visible GRANT MATCH {*} ON GRAPH mydb NODES Company TO limited_reader; // Company props visible // Other labels: traversable but properties invisible
DBA role (full admin)
cypherCREATE ROLE dba IF NOT EXISTS; GRANT ALL ON DBMS TO dba; GRANT ALL ON DATABASE * TO dba;
5. Property-Level Access Control (Enterprise)
Restrict read access to individual properties:
cypher// Grant read on all Person props, then deny sensitive ones GRANT MATCH {*} ON GRAPH mydb NODES Person TO analyst; DENY READ {ssn, dateOfBirth} ON GRAPH mydb NODES Person TO analyst;
Property-based pattern matching (sub-graph access):
cypher// Only see Person nodes where classification = 'public' GRANT MATCH {*} ON GRAPH mydb FOR (n:Person) WHERE n.classification = 'public' TO analyst; // Block access to classified nodes DENY MATCH {*} ON GRAPH mydb FOR (n) WHERE n.classification <> 'UNCLASSIFIED' TO regularUsers; // List-valued property contains a value [2026.08, Cypher 25] GRANT MATCH {*} ON GRAPH mydb FOR (n) WHERE 'gold' IN n.clearanceLevels TO goldTier; GRANT READ {*} ON GRAPH mydb FOR (n) WHERE 'EU' IN n.regions TO regularUsers; GRANT MATCH {*} ON GRAPH mydb FOR (n) WHERE NOT 'EU' IN n.regions TO regularUsers; // Property on the right-hand side of a comparison [2026.08, Cypher 25] GRANT MATCH {*} ON GRAPH mydb FOR (n) WHERE 1 > n.level TO analyst; GRANT READ {*} ON GRAPH mydb FOR (n) WHERE 3 < n.securityLevel TO regularUsers;
value IN n.listProp— list property contains value- Missing or scalar property — no match
- Left value — non-null, not NaN
n.prop IN [v1, v2]— scalar-against-list- Pre-Cypher-25 — keep property on left side of comparison
PBAC edge cases and export patterns → references/privilege-reference.md
Constraints:
FORpattern applies to read privileges only — not write- Each property-based privilege restricted by a single property
- Pre-2026.08: list membership and property-on-RHS predicates are rejected — invert to
n.prop <op> <literal>or maintain a scalar flag property - Performance overhead scales with number of rules;
TRAVERSErules cost more thanREAD - Ensure the property used for rules cannot be modified by the restricted role
6. ABAC — Attribute-Based Access Control (Enterprise)
ABAC grants roles dynamically from OIDC/JWT claims or native user tags rather than explicit GRANT ROLE ... TO user.
Prerequisites
# neo4j.conf — providers must also appear in dbms.security.authorization_providers dbms.security.abac.authorization_providers=<oidc-provider-alias>,native # native [2026.06+]
Unlisted provider → its accessor function (abac.oidc.user_attribute(), abac.native.user_tags()) unavailable and rules referencing it fail (no silent default).
Create auth rule
cypherCREATE AUTH RULE salesRule SET CONDITION abac.oidc.user_attribute('department') = 'sales'; GRANT ROLE analyst TO AUTH RULE salesRule;
Compound conditions
cypherCREATE OR REPLACE AUTH RULE seniorRule SET CONDITION abac.oidc.user_attribute('department') = 'engineering' AND abac.oidc.user_attribute('level') >= 5; GRANT ROLE senior_engineer TO AUTH RULE seniorRule;
Manage auth rules
cypherSHOW AUTH RULES YIELD ruleName, condition, roles; ALTER AUTH RULE salesRule SET ENABLED false; // disable without dropping RENAME AUTH RULE salesRule TO salesDeptRule; DROP AUTH RULE salesDeptRule; REVOKE ROLE analyst FROM AUTH RULE salesRule;
Native users [2026.06+]: tag native DB users (see User tags) and match tags in rules via abac.native.user_tags() — no OIDC required:
cypherCREATE AUTH RULE nativeSalesRule SET CONDITION 'sales' IN abac.native.user_tags(); GRANT ROLE analyst TO AUTH RULE nativeSalesRule; // require several tags at once CREATE AUTH RULE financeAuditRule SET CONDITION all(tag IN ['finance', 'auditor'] WHERE tag IN abac.native.user_tags()); // combine tags with OIDC claims and temporal conditions CREATE AUTH RULE onCallRule SET CONDITION 'on-call' IN abac.native.user_tags() AND abac.oidc.user_attribute('department') = 'engineering' AND time.transaction('UTC').hour >= 9;
❌ Never condition on tag absence: NOT ('restricted' IN abac.native.user_tags()) — any user created without tags satisfies it, escalating privileges.
✅ Require presence of a tag: 'unrestricted' IN abac.native.user_tags().
Notes:
- Missing claims evaluate to NULL → rule condition false → role not granted
- Rules apply immediately to existing sessions when claims are already loaded
- OIDC claims via
abac.oidc.user_attribute(); native user tags viaabac.native.user_tags()[2026.06+]. LDAP has no accessor function — tag the LDAP user's native user object instead - User-defined functions rejected in PBAC property-rule predicates [2026.06+]
- Infinigraph (sharded property databases) supports PBAC
READonly, granted on the virtual database [2026.07+, not on Aura] — see references/privilege-reference.md
7. SHOW PRIVILEGES Patterns
cypher// All privileges in the system SHOW PRIVILEGES YIELD *; // Privileges for a specific user (as runnable commands) SHOW USER alice PRIVILEGES AS COMMANDS; // Privileges for a specific role SHOW ROLE analyst PRIVILEGES YIELD privilege, action, resource, graph, segment; // Privileges of roles granted to an auth rule [2026.09] SHOW AUTH RULES salesRule PRIVILEGES AS COMMANDS; // Find who has access to a database SHOW PRIVILEGES YIELD * WHERE graph = 'mydb' RETURN role, action, resource, segment ORDER BY role; // Find all DENY rules SHOW PRIVILEGES YIELD * WHERE access = 'DENIED' RETURN role, action, resource, segment;
8. Built-in Roles (do not drop)
| Role | Scope |
|---|---|
admin | Full DBMS + all databases |
architect | Schema changes + write on all databases |
publisher | Write on all databases |
editor | Write excluding schema changes |
reader | Read-only on all databases |
public | All users implicitly; default home database access |
Assign built-in roles: GRANT ROLE reader TO alice;
9. Auth Provider Config Reference (operational — not Cypher)
Native (default)
dbms.security.auth_enabled=true dbms.security.auth_max_failed_attempts=3 # lockout threshold
LDAP
dbms.security.auth_provider=ldap dbms.security.ldap.host=ldap://ldap.example.com dbms.security.ldap.authentication.mechanism=simple dbms.security.ldap.authentication.user_dn_template=uid={0},ou=users,dc=example,dc=com dbms.security.ldap.authorization.group_membership_attributes=memberOf dbms.security.ldap.authorization.group_to_role_mapping=\ "cn=analysts,ou=groups,dc=example,dc=com" = analyst;\ "cn=admins,ou=groups,dc=example,dc=com" = admin
OIDC / SSO (Okta, Auth0, Entra ID)
dbms.security.oidc.<alias>.display_name=Okta dbms.security.oidc.<alias>.auth_flow=pkce # `implicit` deprecated 2026.06, removal planned — keep pkce dbms.security.oidc.<alias>.well_known_discovery_uri=https://example.okta.com/.well-known/openid-configuration dbms.security.oidc.<alias>.audience=neo4j dbms.security.oidc.<alias>.claims.username=email dbms.security.oidc.<alias>.claims.groups=groups dbms.security.oidc.<alias>.authorization.group_to_role_mapping=\ "neo4j-analysts" = analyst;\ "neo4j-admins" = admin
Config changes require server restart. Roles referenced in mappings must exist in Neo4j (native or created via Cypher).
Checklist — New Role Setup
- Determine required operations: read / write / admin
- Identify target database(s) and graph scope (all labels vs specific)
- Identify any properties that must be hidden (→ DENY READ)
- Create role:
CREATE ROLE ... IF NOT EXISTS - Grant ACCESS on database
- Grant MATCH / TRAVERSE / WRITE as needed
- Apply DENY for restricted properties
- Run
SHOW ROLE ... PRIVILEGES AS COMMANDSto verify - Assign to users:
GRANT ROLE ... TO ... - Test with
SHOW USER ... PRIVILEGES AS COMMANDS
Full privilege syntax → references/privilege-reference.md

