Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Diagram illustrating Apex security best practices for Salesforce development and secure coding.
Apex

Apex Security Best Practices for Salesforce Developers

Security shouldn't be an afterthought, or something you bolt on the week before a release. Here is how I use User Mode and the platform's own permissions to keep Apex code clean and secure.

The short answer

This guide covers the Apex security practices behind a secure Salesforce application: enforcing user permissions with User Mode, preventing SOQL injection, sanitizing frontend input in controller methods, and automating vulnerability detection with Salesforce Code Analyzer.

Key takeaways Add WITH USER_MODE to SOQL queries and use as user in DML statements to enforce field- and object-level permissions. Prevent SOQL injection by using bind variables in dynamic queries and validating dynamic field names against schema describe information. Protect @AuraEnabled endpoints by instantiating new SObjects in Apex and mapping only the fields users are permitted to modify. Require callers of a shared utility class to pass an AccessLevel parameter to database operations, so nobody escalates privileges by accident. Run Salesforce Code Analyzer in your CI/CD pipeline to catch missing sharing declarations and unsafe SOQL before deployment.

When I review code for a new client, the first thing I look at is how the Apex security best practices are holding up. Too many teams treat security as something you "bolt on" right before a big release or a security review. If you do not build it into your daily workflow, you are leaving a pile of technical debt for your future self to clean up.

Security in Salesforce is layered, and your job in Apex is mostly to respect the permissions the admin already set up. Here is how to do that without over-complicating your life.

1. Stick to the platform security model

Profiles, Permission Sets, and Sharing are your best friends. I have seen developers try to recreate these in code with custom settings or complex logic. Don't do that. If the permissions are set correctly in the org, your job is simply to make sure your Apex actually follows them.

Treat the platform's native security as your first line of defense. When you are deciding between Apex vs Flow for a task, remember that Flow often handles some of this "for free," while Apex requires you to be much more intentional.

2. Implementing Apex security best practices with User Mode

For a long time, we had to write long, annoying blocks of code to check isAccessible() or isUpdateable() for every single field. It was a mess and honestly, most teams got it wrong. Now we have User Mode database operations, probably the most overlooked feature from recent releases.

Using WITH USER_MODE in your SOQL or as user in your DML tells Salesforce to automatically check the user's field-level and object-level permissions. If they don't have access, the platform throws an error. It is clean, it is safe, and it makes your code way easier to read.

// Querying the right way
List<Account> accts = [
    SELECT Name, Industry, SecureField__c
    FROM Account
    WHERE Id = :accountId
    WITH USER_MODE
];

// Updating the right way
update as user new Account(
    Id = someId,
    Name = 'New Name'
);

3. Stop SOQL injection before it starts

I still see people concatenating strings to build dynamic queries. Just don't. It is the easiest way to let a malicious user run code they shouldn't. If you are building a query string, always use bind variables. If you absolutely must use dynamic field names, validate them first against the describe information.

String field = 'Name'; 
String name = 'ACME';

// Validate the field name exists first
String checkedField = Account.SObjectType.getDescribe().fields.getMap().get(field).toString();

// Use a bind variable for the actual data
String query = 'SELECT ' + checkedField + ' FROM Account WHERE Name = :name';
List<SObject> results = Database.query(query);

4. Don't trust frontend input

Every @AuraEnabled method you write is basically a public API. I have seen developers assume that because a field is hidden in the UI, it is safe. Anyone with a browser console can send whatever data they want to your controller. So why do we use the @AuraEnabled annotation? To bridge the gap, but that makes you the gatekeeper.

Always assume the data coming from an LWC is untrusted. Instead of passing a whole SObject into a DML statement, create a new instance of the object in your Apex and only map the fields you actually want to allow the user to change. Combine that with User Mode and you have a solid defense.

One thing that trips people up is "Inherited Sharing." I usually tell my team to avoid it unless they have a very specific reason. Default to "with sharing" so you know exactly how the code will behave.

5. More Apex security best practices for utilities

If you are building a reusable library or a utility class, you might not know ahead of time whether it should run in User Mode or System Mode. The pattern I use is to accept an AccessLevel as a parameter, which forces the person calling your code to be explicit about what they want to happen. No more accidental privilege escalations because someone forgot how a utility worked.

public with sharing class DatabaseService {
    public static List<Database.SaveResult> safeUpdate(List<SObject> records, AccessLevel level) {
        return Database.update(records, level);
    }
}

Automating the boring stuff

You cannot catch everything in a manual code review. We are all human. That is why you need the Salesforce Code Analyzer. It is a command-line tool that plugs right into your CI/CD pipeline, and it will flag things like missing sharing keywords or unsafe SOQL before the code even gets to a sandbox. In my experience, setting this up once saves dozens of hours of cleanup later.

Key takeaways

  • Default to User Mode. Use WITH USER_MODE and as user for almost everything.
  • Be explicit with sharing. Use with sharing by default and avoid inherited sharing.
  • Validate inputs. Never trust what comes from the frontend or from dynamic strings.
  • Use bind variables. It is the simplest way to prevent SOQL injection.
  • Automate. Let static analysis tools find the easy mistakes for you.

Final thoughts

Apex security best practices are really about reducing your blast radius. The modern tools Salesforce has given us, User Mode and the Code Analyzer, let you write less code and make it much more secure at the same time. That keeps your customer data safe and saves you some nasty surprises during your next security audit. So start small. Try switching your next batch of queries to User Mode and see how much cleaner it feels.

Frequently asked questions

How do you enforce field-level security in Apex?

Add WITH USER_MODE to SOQL queries, or the as user clause to DML operations. Salesforce then verifies the running user's object- and field-level permissions and throws an error if access is insufficient.

How do you prevent SOQL injection in dynamic queries?

Do not concatenate raw strings into a query statement. Use bind variables for user input, and when field names have to be dynamic, validate them against the schema describe field map before you execute Database.query.

How do you secure AuraEnabled methods against untrusted input?

Treat incoming frontend data as untrusted, and do not pass client-provided SObjects directly into DML operations. Instantiate a new SObject in Apex, populate only the allowed fields, and perform the operation in user mode.

Why should you pass AccessLevel to utility methods in Apex?

An AccessLevel parameter forces the calling code to choose explicitly between user mode and system mode. That prevents the accidental privilege escalation you get when a developer assumes a utility defaults to one security context or the other.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment