Why Apex Runs in System Mode by Default
Unless explicitly told otherwise, Apex executes in system mode: object and field-level security (CRUD/FLS) and record-level sharing rules are bypassed entirely, meaning a class can read, write, or delete any record regardless of the running user's permissions. This is powerful but dangerous — a controller behind a Visualforce page or Lightning component that queries Salary__c without checking field permissions will happily return that data to a low-privilege user who could never see it through the standard UI. The class-level with sharing and without sharing keywords control record-level sharing enforcement only; they do NOT enforce CRUD or field-level security, which must be handled separately.
Cricket analogy: System mode is like a curator with a master key who can walk into any team's dressing room regardless of accreditation — powerful for ground staff, but dangerous if that same access lets anyone view opposition team strategy notes.
Enforcing Sharing, CRUD, and Field-Level Security
Declaring a class public with sharing class MyController causes SOQL queries and DML within it to respect the running user's record-level sharing rules — role hierarchy, organization-wide defaults, sharing rules, and manual shares — so a user without access to a given Account record simply won't see it in query results. This is distinct from object and field permissions: even a with sharing class will still return every field on a record the user can see unless you separately check CRUD/FLS. For that, use WITH SECURITY_ENFORCED in inline SOQL to throw an exception if the running user lacks access to any referenced field, or the Security.stripInaccessible() method to silently remove inaccessible fields from a result set (useful when you want to continue execution rather than throw).
Cricket analogy: with sharing enforcing record visibility is like the DRS system only showing a player the replays their team is entitled to review, based on their review count — not everyone sees everything by default.
public with sharing class ContactSecureController {
@AuraEnabled(cacheable=true)
public static List<Contact> getVisibleContacts(String accountId) {
// Record-level sharing is respected because the class is 'with sharing'.
// WITH SECURITY_ENFORCED additionally throws if the user lacks FLS
// on any selected field (Name, Email, Phone).
return [
SELECT Id, Name, Email, Phone
FROM Contact
WHERE AccountId = :accountId
WITH SECURITY_ENFORCED
ORDER BY Name
];
}
@AuraEnabled
public static void updateContact(Contact con) {
// Explicit CRUD check before DML, since DML alone does not
// automatically enforce object-level create/update permission.
if (!Schema.sObjectType.Contact.fields.Email.isUpdateable()) {
throw new AuraHandledException('You do not have permission to update Email.');
}
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.UPDATABLE, new List<Contact>{ con }
);
update decision.getRecords();
}
}when to use without sharing
without sharing intentionally runs a class in the current user's context for CRUD/FLS purposes but bypasses record-level sharing, so it can read or modify records the running user couldn't normally see through role hierarchy or sharing rules. This is appropriate for utility classes that must aggregate data across the whole org regardless of ownership — a dashboard rollup, a duplicate-detection scan, or a trigger helper that needs to update a related record the current user doesn't own (e.g., updating a parent Account's rollup field when a child Opportunity closes). It should never be the default choice; every without sharing class deserves a code comment explaining exactly why bypassing sharing is necessary, because it is the most common source of unintended data exposure in Apex.
Cricket analogy: without sharing is like a third umpire's special camera access to review angles no single on-field umpire could see — a deliberate, justified exception to normal viewing rules for a specific, necessary purpose.
If a class has no explicit with sharing or without sharing keyword, it inherits the sharing mode of whatever class called it. If it is invoked directly as an entry point (e.g., a Visualforce controller or an @AuraEnabled method with no caller context), it runs without sharing by default. Always be explicit rather than relying on inherited behavior.
CRUD and FLS Checks Before DML
Sharing keywords and WITH SECURITY_ENFORCED handle read-side visibility, but write operations need their own checks: before an insert, update, or delete, verify the running user's object permission with Schema.sObjectType.Contact.isCreateable(), isUpdateable(), or isDeletable(), and field-level access with the corresponding DescribeFieldResult methods. In Lightning/Aura and LWC contexts specifically, @AuraEnabled Apex methods are a common attack surface because they can be invoked directly from client-side JavaScript with attacker-controlled parameters, bypassing whatever the UI normally restricts — so server-side CRUD/FLS checks in the Apex method itself, not just component-level field visibility, are the real security boundary.
Cricket analogy: Checking isUpdateable() before a DML write is like a scorer double-checking a player's actual dismissal type against the umpire's signal before amending the official scorecard — verify before you commit the change.
@AuraEnabled methods exposed to Lightning components are directly callable by any authenticated user who can reach the component, including via browser dev tools or crafted requests — component-level lightning:isAvailableAction or field visibility settings in the Lightning App Builder provide zero protection on their own. Always enforce CRUD/FLS and sharing inside the Apex method itself.
- Apex runs in system mode by default, bypassing both record-level sharing and CRUD/FLS unless explicitly enforced.
with sharingenforces record-level sharing rules only; it does not check object or field-level permissions.- WITH SECURITY_ENFORCED in SOQL throws if the running user lacks FLS on any queried field; stripInaccessible() silently filters instead.
without sharingshould be rare and always documented with a clear justification for bypassing record visibility.- Write operations need explicit isCreateable()/isUpdateable()/isDeletable() checks before DML — sharing keywords don't cover writes.
- @AuraEnabled methods are a direct client-callable attack surface; server-side checks are the real security boundary, not UI restrictions.
- A class with no sharing keyword inherits its caller's sharing mode, or defaults to without sharing if invoked directly.
Practice what you learned
1. What does the `with sharing` keyword on an Apex class control?
2. What is the difference between WITH SECURITY_ENFORCED and Security.stripInaccessible()?
3. Why are @AuraEnabled Apex methods considered a significant security surface?
4. What sharing mode does a class run in if it has no `with sharing` or `without sharing` keyword and is invoked directly (e.g., as a Visualforce controller)?
5. Before performing a DML update in Apex, what should be explicitly checked to respect field-level security?
Was this page helpful?
You May Also Like
Testing Apex Code
Learn how to write robust Apex test classes, use assertions and mocks, and produce meaningful code coverage that survives real deployments.
Apex Design Patterns
Explore the Trigger Handler, Service Layer, Selector, and Unit of Work patterns that keep large Apex codebases maintainable, testable, and bulk-safe.
Governor Limits
Understand why Salesforce enforces per-transaction governor limits and how to write bulk-safe Apex that never hits SOQL, DML, CPU, or heap ceilings.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics