100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Security and Sharing in Apex

Learn how CRUD/FLS enforcement, sharing keywords, and the WITH SECURITY_ENFORCED / stripInaccessible tools keep Apex code from leaking data users shouldn't see.

Practical ApexAdvanced10 min readJul 10, 2026
Analogies

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.

apex
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 sharing enforces 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 sharing should 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

Was this page helpful?

Topics covered

#Programming#ApexSalesforceStudyNotes#SecurityAndSharingInApex#Security#Sharing#Apex#Runs#StudyNotes#SkillVeris#ExamPrep