How to Architect Secure Apex in Salesforce — Practical Strategies for Developers

Security must be a default, not an afterthought. This post breaks down practical patterns—User Mode, safe SOQL, controlled @AuraEnabled inputs, sharing models, and CI static analysis—to help you write secure Apex that stays secure.

Security in Apex is multi-layered. Follow these techniques to reduce risk, simplify code, and make security checks reliable and automated.

1. Start with the Platform Security Model

Profiles, Permission Sets, and Sharing are the foundation. If permissions are configured correctly, they will be enforced across UI, API, reports, and integrations. Treat permissions as the first line of defense and verify them early in your development process.

2. Prefer User Mode for Queries and DML

Spring ’23 introduced User Mode database operations which automatically respect field- and object-level permissions. Using User Mode simplifies code and replaces verbose manual access checks.

// Query in User Mode
List<Account> accts = [
    SELECT Name, Industry, SecureField__c
    FROM Account
    WHERE Id = :accountId
    WITH USER_MODE
];

// Inline DML as user
update as user new Account(
    Id = toUpdate.Id,
    Name = toUpdate.Name,
    Industry = toUpdate.Industry,
    SecureField__c = toUpdate.SecureField__c
);

3. Avoid SOQL Injection — Use Binds and Concrete Types

Never concatenate untrusted strings into queries. Use bind variables (WHERE Name = :name) and validate any dynamic field or SObject names against describe information before including them in a query.

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

String checkedField = Account.SObjectType.getDescribe().fields.getMap().get(field).toString();
String query = 'SELECT ' + checkedField + ' FROM Account WHERE Name = :name';
List<SObject> results = Database.query(query);

4. Treat Frontend Input as Untrusted

Every @AuraEnabled method is an API surface. Do not rely on the UI to filter fields. Accept only the fields you intend to update and perform DML in User Mode to let the platform block unauthorized writes.

@AuraEnabled
public static void updateAccountUserMode(Account toUpdate) {
    update as user new Account(
        Id = toUpdate.Id,
        Name = toUpdate.Name,
        Industry = toUpdate.Industry
    );
}

5. Use with sharing and Avoid Inherited Sharing

Default to with sharing on classes that access the database. Inherited sharing can silently propagate elevated privileges; prefer explicit sharing declarations and elevate to without sharing only for the smallest, well-justified scopes.

6. Provide Intentional AccessLevel for Utilities

For reusable database utilities, accept an AccessLevel parameter (System.AccessLevel.USER_MODE or SYSTEM_MODE) so callers must be explicit about the intended mode. This pattern reduces accidental privilege escalations across call chains.

public with sharing class MyUtil {
    private AccessLevel accessLevel;
    private List<SObject> records;

    public MyUtil() {
        accessLevel = System.AccessLevel.USER_MODE;
    }
    public MyUtil(AccessLevel accessLevel) {
        this.accessLevel = accessLevel;
    }

    @SuppressWarnings
    public List<Database.SaveResult> updateRecords() {
        return Database.update(records, accessLevel);
    }
}

7. Automate Security Checks with Static Analysis and CI

Use Salesforce Code Analyzer (which includes PMD and other scanners) and add custom rules to enforce User Mode for database access. Integrate checks into pre-commit hooks and CI, and require documented justification for any @SuppressWarnings or intentional relaxations.

  • Run Code Analyzer in local IDEs (VS Code, Illuminated Cloud).
  • Add CI gates that fail builds on security rule violations.
  • Document and review any suppressed warnings in PRs.

Final Thoughts

Modern Apex features make it easier to write secure, minimal code. By defaulting to User Mode, validating dynamic inputs, avoiding inherited sharing, and automating checks with static analysis, teams can significantly reduce the surface for security issues while keeping code clean and maintainable.

Why this matters: secure defaults protect customer data, reduce incident risk, and keep ISV products passing Security Review. For admins, developers, and architects, adopting these patterns means fewer emergencies and better compliance.