Apex Security
Apex Security: A Comprehensive Guide
Your Salesforce Coach Explains How to Secure Your Custom Code
1. What is Apex Security?
Imagine Salesforce as a high-security bank. When users interact with the standard user interface (like clicking buttons or editing records), the bank’s built-in security guards (profiles, permission sets, sharing rules, etc.) are always checking their credentials and access levels before allowing any operation. This is standard Salesforce security.
Now, imagine you write a custom program (Apex code) to perform complex operations, like automatically updating hundreds of records or fetching data for a custom user interface. By default, Apex code runs in a special “system mode.” Think of this as giving your custom program a skeleton key – it can potentially bypass those standard security guards!
Apex Security is all about making sure that even when your Apex code runs with its powerful “system mode” capabilities, it respects the data access and permissions that the *end-user* would normally have. It’s about preventing your code from inadvertently or maliciously exposing sensitive data or performing unauthorized actions on behalf of a user who shouldn’t have that access. It ensures that your custom logic doesn’t become a loophole in your organization’s security posture.
2. Why is it Important?
Apex Security isn’t just a technical detail; it’s fundamental to the integrity and trustworthiness of your Salesforce org and the data it holds. Here’s why it’s crucial:
- Data Integrity & Confidentiality: Without Apex Security, your custom code could accidentally (or intentionally) expose sensitive customer data to users who don’t have permission to see it, or allow users to modify records they shouldn’t. This can lead to compliance violations (e.g., GDPR, HIPAA), loss of trust, and significant financial or reputational damage.
- Compliance & Auditing: Many industry regulations require strict access control over data. Proper Apex Security helps you maintain compliance by ensuring that even programmatic access adheres to defined policies, making your org easier to audit.
- Preventing Unauthorized Actions: If a user can trigger an Apex method that creates or updates records they don’t normally have access to, your entire security model is compromised. Apex Security prevents this bypass, ensuring that users can only initiate actions that align with their assigned permissions.
- Robust & Predictable Applications: When security is consistently applied, your applications behave predictably. Users get the right data, and only the right data, preventing confusion and errors.
- Building Trust: Users and stakeholders trust that their data is safe in Salesforce. Proper security implementation reinforces this trust.
Think of it this way:
Accesses/Modifies ANYTHING!
Accesses/Modifies ONLY what User CAN!
3. Key Concepts & Components
Apex security revolves around two main pillars: controlling sharing (record-level access) and enforcing CRUD & FLS (object and field-level access).
3.1. Sharing (Record-Level Security)
This determines which records a user can see or interact with. Standard Salesforce security (OWD, sharing rules, roles, permission sets) handles this for the UI. In Apex, you control whether your code respects these rules.
-
with sharing: When you declare an Apex class or method withwith sharing, it means the code will enforce the user’s sharing rules. If the user doesn’t have access to a specific record via OWD, roles, sharing rules, etc., your Apex code won’t be able to query, update, or delete that record.public with sharing class MySecureService { public static List<Account> getMyAccounts() { return [SELECT Id, Name FROM Account]; // Only returns accounts user can see } }Analogy: The “bouncer” for the class checks everyone’s ID at the door, ensuring only permitted users (and their code) get in to see specific records.
-
without sharing: This keyword tells Apex to ignore the user’s sharing rules. The code will have access to all records of the objects it’s querying, regardless of the running user’s permissions. Use this with extreme caution! It’s typically used for operations where the code itself needs elevated access to fulfill a function, like a system-level batch process.public without sharing class MySystemService { public static void deleteAllAccounts() { // DANGEROUS EXAMPLE! // This code could potentially delete all accounts in the org, regardless of user sharing! delete [SELECT Id FROM Account]; } }Analogy: The “backdoor key” for the class. It bypasses the bouncer entirely.
-
inherited sharing: (Introduced Summer ’23) This is the best practice for most new Apex classes. Aninherited sharingclass runs in the sharing mode of the class or method that called it. If it’s called by awith sharingclass, it runswith sharing. If called by awithout sharingclass, it runswithout sharing. This promotes flexibility and reusability, allowing the calling context to dictate the sharing behavior.public inherited sharing class MyFlexibleService { public static List<Contact> getContactsForAccount(Id accountId) { return [SELECT Id, Name FROM Contact WHERE AccountId = :accountId]; } } // Example usage: public with sharing class CallingWithSharing { public void doSomething() { // MyFlexibleService will run with sharing here List<Contact> contacts = MyFlexibleService.getContactsForAccount('001...'); } }Analogy: The “chameleon” class. It adapts its security behavior to whatever called it.
-
Default Behavior: If you don’t specify
with sharing,without sharing, orinherited sharingfor a class, it defaults towithout sharing, meaning it ignores record-level security. This is a common pitfall!
Apex Class Sharing Model Flow
without sharing (System Mode)with sharinginherited sharing3.2. CRUD & FLS (Object and Field-Level Security)
Even if your Apex code runs with sharing (respecting record access), it still runs in “system mode” by default for object and field permissions. This means it can see and modify any object or field regardless of the running user’s profile or permission sets!
To enforce CRUD (Create, Read, Update, Delete) and FLS (Field-Level Security), you need to explicitly check permissions within your Apex code.
Methods for CRUD & FLS Enforcement:
-
Schema.SObjectAccessDecisionandSecurity.stripInaccessible(): (Recommended for SOQL/SOSL results) This static method takes a list of sObjects and the access type (READABLE,UPDATABLE,CREATABLE, etc.) and returns a list containing only the sObjects and fields the user has access to. It automatically strips out inaccessible fields and removes records the user can’t access for the specified operation.List<Account> accounts = [SELECT Id, Name, AnnualRevenue, Phone FROM Account LIMIT 10]; // Enforce FLS and CRUD for readable fields/objects SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, accounts); accounts = securityDecision.getRecords(); // Now 'accounts' only contains fields and records the current user can see. // Example: if user can't see AnnualRevenue, that field will be null in the returned list. // If user can't see an Account record, it will be removed from the list. -
SchemaDescribe Methods: These methods allow you to programmatically check if a user has access to an object or field. You use them *before* performing an operation.Schema.sObjectType.MyObject__c.isAccessible(): Can the user read this object?Schema.sObjectType.MyObject__c.isCreatable(): Can the user create records of this object?Schema.sObjectType.MyObject__c.isUpdateable(): Can the user update records of this object?Schema.sObjectType.MyObject__c.isDeletable(): Can the user delete records of this object?Schema.sObjectType.MyObject__c.fields.MyField__c.isAccessible(): Can the user read this field?Schema.sObjectType.MyObject__c.fields.MyField__c.isUpdateable(): Can the user update this field?Schema.sObjectType.MyObject__c.fields.MyField__c.isCreateable(): Can the user create/set this field?
if (!Schema.SObjectType.Account.isAccessible()) { throw new AuraHandledException('You do not have permission to view Accounts.'); } List<Account> accs = [SELECT Id, Name FROM Account]; if (!Schema.SObjectType.Account.fields.AnnualRevenue.isAccessible()) { // Handle case where AnnualRevenue is not accessible System.debug('AnnualRevenue field is not visible to the user.'); } -
WITH USER_MODEandWITH SYSTEM_MODE(Introduced Winter ’24): These keywords can be directly added to SOQL, SOSL, and DML statements to explicitly define the execution mode for *that specific database operation*.-
WITH USER_MODE: Executes the database operation by enforcing the running user’s CRUD and FLS permissions. This is a powerful, concise way to apply user permissions directly to your queries and DML, significantly reducing the need for verboseSchemachecks orstripInaccessiblein many common scenarios.List<Account> userAccounts = [SELECT Id, Name, AnnualRevenue FROM Account WITH USER_MODE]; // userAccounts will only contain records/fields the user can see. // If user can't see AnnualRevenue, it won't be queried or will be null. // If user can't see an Account, it won't be returned. Account newAcc = new Account(Name='Test User Mode Acc', AnnualRevenue=10000); insert newAcc WITH USER_MODE; // Only inserts if user has create permission on Account and FLS for fields.This is a game-changer for enforcing CRUD/FLS in SOQL/SOSL/DML!
-
WITH SYSTEM_MODE: Executes the database operation in full system mode, bypassing all user CRUD and FLS permissions. This is the default behavior if no mode is specified for SOQL/SOSL/DML within an Apex class that does not useWITH USER_MODE. Use this only when truly necessary for system-level operations, similar to when you’d usewithout sharing.List<User> allUsers = [SELECT Id, Name, IsActive FROM User WITH SYSTEM_MODE]; // This will return all users, even if the running user doesn't have FLS on IsActive or access to some users. delete [SELECT Id FROM Opportunity WHERE StageName = 'Closed Lost'] WITH SYSTEM_MODE; // DANGEROUS EXAMPLE!
-
CRUD/FLS Enforcement Options
Security.stripInaccessible(AccessType.READABLE, records)
Strips inaccessible records & fields from query results.
Schema.sObjectType...isAccessible() methods
Explicitly check permissions before SOQL/DML.
SOQL/SOSL/DML WITH USER_MODE
Enforce user CRUD/FLS directly in query/DML (Recommended Modern Approach).
4. How Does It Work? (A Step-by-Step Breakdown)
Let’s put it all together to understand the flow when Apex code executes:
Step 1: Apex Class/Method Execution (Initial Context)
When a user (or another process) invokes an Apex class or method, the platform first determines its sharing context based on the class declaration:
- If
without sharing: Record-level security (sharing) is ignored. - If
with sharing: Record-level security (sharing) for the running user is enforced. - If
inherited sharing: The sharing setting of the calling code’s context is inherited. If the call originates from an external system or UI element that doesn’t explicitly define a sharing context, it usually defaults towithout sharing. - If no keyword: Defaults to
without sharing(DANGER ZONE!).
Crucial Point: Regardless of the sharing keyword, Apex code still runs in “system mode” for CRUD and FLS by default at this stage. This means it *can* see all objects and fields unless you tell it otherwise.
Step 2: Database Operations (SOQL, SOSL, DML)
When your Apex code tries to interact with the database (e.g., query records, insert new ones, update existing ones), this is where you explicitly apply security:
-
For Record-Level Security (Sharing):
The sharing context from Step 1 (
with sharing,without sharing, orinherited sharing) is applied to the database operations. Ifwith sharingis in effect, SOQL queries will only return records the user has access to, and DML operations will only succeed on records the user can modify/delete. -
For Object & Field-Level Security (CRUD & FLS):
This is where you explicitly step in. If you don’t do anything, the code will bypass CRUD/FLS because it’s running in system mode. To enforce it, you have three primary modern options:
-
WITH USER_MODE(Recommended): AddWITH USER_MODEdirectly to your SOQL/SOSL query or DML statement. This is the most direct and declarative way to ensure CRUD and FLS are applied for that specific database interaction. The query will only return fields and records the user has permission to see, and DML will only succeed if the user has the necessary object and field permissions. -
Security.stripInaccessible(): After executing a SOQL/SOSL query in system mode (withoutWITH USER_MODE), pass the results toSecurity.stripInaccessible(). This method filters out records the user can’t access and sets inaccessible fields to null, effectively enforcing CRUD/FLS *after* the initial query. -
SchemaDescribe Methods: Before executing your SOQL/SOSL or DML, use methods likeisAccessible(),isCreatable(),fields.MyField__c.isUpdateable()to check permissions. If the user doesn’t have access, you’d then prevent the operation or display an error. This is more verbose but gives fine-grained control.
-
Step 3: Returning Results or Completing Operations
After the database operations, the Apex code returns the results or completes its tasks. If security was properly enforced in Step 2, the user will only receive data or see the effects of operations that align with their assigned permissions. If security was *not* enforced, the user might see or cause changes they are not authorized for, leading to a security breach.
Apex Security Flow Diagram
with sharing | without sharing | inherited sharing (or default without sharing)
- Record Access: Enforced by class’s sharing context.
- Object/Field Access (CRUD/FLS):
- Use
WITH USER_MODE(Recommended) - OR use
Security.stripInaccessible() - OR use
SchemaDescribe Methods
- Use
Data/Actions respect user’s full permissions.
5. Best Practices & Common Pitfalls
Best Practices:
-
Always Use
inherited sharingfor new classes: This is the most flexible and secure default. It ensures your code adapts to the caller’s security context, promoting reusability and reducing security vulnerabilities. -
Use
with sharingifinherited sharingisn’t an option or if you need to strictly enforce user permissions regardless of the caller: This makes it explicit that your class respects record-level security. -
Enforce CRUD & FLS: Never assume the running user has access to objects or fields.
- For SOQL/SOSL/DML, use
WITH USER_MODE: This is the most modern, concise, and recommended way to apply CRUD and FLS checks directly to your database operations. - If not using
WITH USER_MODE, useSecurity.stripInaccessible(): This is excellent for filtering query results to prevent data exposure. - For fine-grained, pre-emptive checks, use
SchemaDescribe Methods: Ideal when you need to check permissions before attempting an operation and provide specific error messages.
- For SOQL/SOSL/DML, use
-
Document
without sharing: If you absolutely must usewithout sharing(e.g., for integrations, batch jobs, or system-level processes), document clearly *why* it’s necessary and what safeguards are in place (e.g., Is the data exposed to users only through other secure components? Is the method only called by trusted system processes?). - Validate All Inputs: Even with sharing and FLS, always validate user inputs to prevent issues like SOQL injection (though less common with parameter binding) or unexpected behavior.
-
Test Security: Write unit tests that explicitly verify both positive (user has access) and negative (user lacks access) security scenarios. Use
System.runAs()to test as different users.
Common Pitfalls:
-
Forgetting a Sharing Keyword: If you don’t specify
with sharing,without sharing, orinherited sharing, the class defaults towithout sharing. This is a common and dangerous oversight, silently bypassing record-level security. -
Over-reliance on
without sharing: Usingwithout sharingwhen it’s not strictly necessary. Each instance increases your security risk. -
Ignoring CRUD/FLS: Assuming that because you’ve used
with sharing, your code is fully secure. Remember,with sharingonly handles record-level access; CRUD/FLS still need explicit enforcement (WITH USER_MODE,stripInaccessible, or Schema methods). - Hardcoding Object/Field Names: While not a security flaw directly, it makes maintaining security checks harder. Use dynamic SOQL or describe calls cautiously.
- Not testing as different users: Your code might work perfectly when you, the admin, test it, but fail or expose data when a standard user runs it.
- Inadequate Exception Handling: If a security check fails, your code should handle it gracefully, providing informative (but not revealing) error messages to the user and logging details for administrators.
6. What’s New in Apex Security?
Salesforce consistently enhances its platform security. Here are some of the most impactful recent (as of late 2023/early 2024, generally considered “new” for standard adoption) features related to Apex Security, making it easier and more robust to write secure code:
-
inherited sharingKeyword (Summer ’23): This addition provides a flexible and secure default for new Apex classes, allowing them to adapt their sharing behavior to the calling context, which greatly simplifies developing reusable and secure components. -
WITH USER_MODEandWITH SYSTEM_MODEfor SOQL, SOSL, and DML (Winter ’24): These keywords allow developers to directly specify the security context (user or system) for individual database operations, making CRUD and FLS enforcement much more straightforward and declarative within queries and DML statements themselves. -
Security.stripInaccessible()Enhancements: Salesforce continues to refine this method, making it more efficient and robust for filtering query results according to user permissions, ensuring no inaccessible data is exposed. (Continuous improvements are often made across releases without a single “new” banner for the method itself).
While specific features vary by release (e.g., Winter ’26, Spring ’26 aren’t out yet), the shift towards declarative security enforcement and flexible sharing models like inherited sharing and WITH USER_MODE represents the ongoing direction for Apex security improvements.
7. Related Documents & Resources
To dive deeper into Apex Security, explore these valuable resources:
Salesforce Official Documentation:
- Using the with sharing, without sharing, and inherited sharing Keywords
- Enforcing Object and Field-Level Permissions
- SOQL and SOSL Queries in User Mode and System Mode
- Trailhead: Apex Basics & Database – Apex Security (Great starting point!)
Blogs & Videos:
- YouTube: Salesforce Apex Sharing | with sharing | without sharing | inherited sharing | Apex Security (SFDCStop) – A good video explanation.
- SFDC Stop: Apex Inherited Sharing – Detailed blog post on the new keyword.
- Salesforce Stack Exchange: Apex Security – Community discussions and solutions for specific security challenges.
- Bhavi.me: WITH USER_MODE and WITH SYSTEM_MODE in Apex SOQL and DML Statements – A clear explanation of the latest declarative security features.

