Skip to content

Programmatic Security: Apex Enforcement & Secure Coding

Programmatic security covers how developers enforce security in Apex code, prevent common vulnerabilities, and securely integrate with external systems. This is CTA Objective 2.4 — and the board expects you to know when declarative security is insufficient and how to enforce security correctly in code.

The Sharing Keyword Spectrum

Every Apex class runs in one of three sharing contexts. Choosing the wrong one is a common security vulnerability.

with sharing / without sharing / inherited sharing

KeywordRecord AccessUse Case
with sharingEnforces the running user’s sharing rules (OWD, role hierarchy, sharing rules)Default for most classes — user-facing logic
without sharingIgnores all sharing rules — sees all recordsSystem operations, integrations, admin utilities
inherited sharingInherits the sharing context of the calling classUtility classes called from multiple contexts
(no keyword)Inherits sharing context from calling class; defaults to without sharing only when used as the entry point of an Apex transactionAvoid — explicit is always better
flowchart TD
    Q1{"Is this class user-facing\n(controller, service for UI)?"}
    Q1 -->|Yes| WithSharing["Use 'with sharing'\nEnforce user's record access"]
    Q1 -->|No| Q2{"Is this a utility/helper\ncalled from multiple contexts?"}
    Q2 -->|Yes| Inherited["Use 'inherited sharing'\nInherit caller's context"]
    Q2 -->|No| Q3{"Does it NEED to see\nall records regardless\nof user access?"}
    Q3 -->|Yes| WithoutSharing["Use 'without sharing'\nDocument WHY"]
    Q3 -->|No| WithSharing2["Use 'with sharing'\nDefault to restrictive"]

    style WithSharing fill:#2e7d32,color:#fff
    style WithSharing2 fill:#2e7d32,color:#fff
    style WithoutSharing fill:#c62828,color:#fff
    style Inherited fill:#1565c0,color:#fff

The “without sharing” Risk

Every class marked without sharing is a potential security bypass. The CTA board will question any architecture that uses without sharing extensively. Always document WHY a class needs to bypass sharing, and keep the scope as narrow as possible — ideally a single inner class method that does the elevated query, not the entire outer class.

Sharing Context Interaction

flowchart LR
    subgraph "Call Chain"
        A["Controller\n(with sharing)"] --> B["Service\n(with sharing)"]
        B --> C["Utility\n(inherited sharing)"]
        C --> D["Data Access\n(without sharing)"]
    end

    subgraph "Effective Sharing"
        EA["with sharing"] -.-> EB["with sharing"]
        EB -.-> EC["with sharing\n(inherited from B)"]
        EC -.-> ED["without sharing\n(overrides)"]
    end

    style A fill:#2e7d32,color:#fff
    style B fill:#2e7d32,color:#fff
    style C fill:#1565c0,color:#fff
    style D fill:#c62828,color:#fff

inherited sharing Behavior

inherited sharing in the top-level context (e.g., a trigger or anonymous Apex) runs as with sharing. It only inherits a different context when called from another class. This makes it safe as a default for utility classes.

CRUD and FLS Enforcement

Sharing controls which records a user sees. CRUD and FLS control what operations they can perform and which fields they can access. Apex does NOT automatically enforce CRUD or FLS — developers must enforce it explicitly.

Enforcement Methods Comparison

MethodEnforcesAvailable SinceWhere It Works
WITH SECURITY_ENFORCEDObject CRUD + FLS (Read only)Spring ‘20 (GA)SOQL queries
WITH USER_MODECRUD + FLS + SharingSpring ‘23SOQL + DML
stripInaccessible()FLSSpring ‘20 (GA)Apex (before DML or after query)
Schema.describeManual checkAlwaysApex (programmatic checks)
Security.stripInaccessible()FLS per operation typeSpring ‘20 (GA)Apex

WITH SECURITY_ENFORCED

Appended to a SOQL query to enforce FLS on all referenced fields. If the running user lacks Read access to any field in the query, a System.QueryException is thrown.

// Enforces FLS -- throws exception if user can't read any field
List<Account> accounts = [
SELECT Name, Phone, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
WITH SECURITY_ENFORCED
];

Limitations:

  • Read-only enforcement (does not apply to DML)
  • Throws an exception rather than stripping fields — all or nothing
  • Enforces both object-level CRUD and field-level security on SOQL SELECT queries
  • Does not enforce sharing (use with sharing for that)

WITH USER_MODE / WITH SYSTEM_MODE

The modern, comprehensive approach. Available on both SOQL and DML.

// USER_MODE: Enforces CRUD + FLS + sharing
List<Account> accounts = [
SELECT Name, Phone, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
WITH USER_MODE
];
// USER_MODE on DML: Enforces CRUD + FLS
Database.insert(newAccounts, AccessLevel.USER_MODE);
Database.update(existingAccounts, AccessLevel.USER_MODE);
// SYSTEM_MODE: Bypasses CRUD + FLS + sharing (explicit bypass)
List<Account> allAccounts = [
SELECT Name, Phone, AnnualRevenue
FROM Account
WITH SYSTEM_MODE
];

Advantages over WITH SECURITY_ENFORCED:

  • Works on both SOQL and DML (SECURITY_ENFORCED is SOQL-only)
  • Inaccessible fields are silently stripped (no exception)
  • Enforces sharing rules automatically
  • Single consistent API for all security enforcement

CTA Recommendation

For new development, recommend USER_MODE as the default for all SOQL and DML operations. It provides the most comprehensive security enforcement with the least code. Use SYSTEM_MODE only for system operations that explicitly need to bypass user permissions.

stripInaccessible()

Strips fields the running user cannot access before DML or after a query. Provides granular control over which operation type to check.

// Strip fields user can't read from query results
List<Account> accounts = [SELECT Name, Phone, AnnualRevenue FROM Account];
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE,
accounts
);
List<Account> sanitized = decision.getRecords();
// sanitized records have inaccessible fields removed
// Strip fields user can't create before insert
List<Account> newAccounts = new List<Account>();
// ... populate accounts
SObjectAccessDecision insertDecision = Security.stripInaccessible(
AccessType.CREATABLE,
newAccounts
);
insert insertDecision.getRecords();

AccessType options: READABLE, CREATABLE, UPDATABLE, UPSERTABLE

Enforcement Selection Guide

flowchart TD
    Start["Need to enforce\nCRUD/FLS in Apex"] --> Q1{"SOQL query\nor DML?"}

    Q1 -->|"SOQL"| Q2{"Need CRUD + FLS\nor just FLS?"}
    Q2 -->|"CRUD + FLS"| UserMode["WITH USER_MODE\n(Recommended)"]
    Q2 -->|"CRUD + FLS (SOQL only)"| Q3{"Want exception\nor strip fields?"}
    Q3 -->|"Exception"| SecEnf["WITH SECURITY_ENFORCED"]
    Q3 -->|"Strip silently"| Strip1["Query + stripInaccessible()"]

    Q1 -->|"DML"| Q4{"Want comprehensive\nenforcement?"}
    Q4 -->|"Yes"| UserModeDML["Database.op(records, USER_MODE)\n(Recommended)"]
    Q4 -->|"Granular control"| Strip2["stripInaccessible()\nbefore DML"]

    style UserMode fill:#2e7d32,color:#fff
    style UserModeDML fill:#2e7d32,color:#fff
    style SecEnf fill:#1565c0,color:#fff

Named Credentials

Named Credentials store callout endpoint URLs and authentication details, keeping secrets out of Apex code. They are essential for secure integrations.

Why Named Credentials Matter

Without Named CredentialsWith Named Credentials
Credentials hardcoded or in Custom SettingsCredentials managed declaratively
Endpoint URLs in codeEndpoint URL configured in Setup
OAuth token management in ApexPlatform handles token lifecycle
Secrets in version controlSecrets stored securely by platform
Each developer manages authAuth configured once, used everywhere

Named Credential Usage in Apex

// Callout using Named Credential -- no auth code needed
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_ERP_System/api/orders'); // Named Credential prefix
req.setMethod('GET');
Http http = new Http();
HttpResponse res = http.send(req);
// Platform automatically adds authentication headers

Named Credential vs Remote Site Setting

FeatureNamed CredentialRemote Site Setting
Stores auth detailsYesNo
Token managementAutomatic (OAuth)Manual (in code)
CSRF protectionBuilt-inManual
Per-user vs Named PrincipalBothN/A
Use caseAuthenticated calloutsUnauthenticated callouts only

CTA Architecture Rule

Always recommend Named Credentials for callouts. If a CTA scenario shows Apex code with hardcoded credentials or manual OAuth token management, flag it as a security risk and recommend Named Credentials as the fix. The board values this.

Apex Crypto

The Crypto class provides cryptographic functions for signing, encrypting, hashing, and generating MACs.

Common Crypto Use Cases

OperationMethodUse Case
HashingCrypto.generateDigest('SHA-256', data)Data integrity verification
HMACCrypto.generateMac('HmacSHA256', data, key)Webhook signature validation
EncryptionCrypto.encrypt('AES256', key, iv, data)Encrypt sensitive data in transit
DecryptionCrypto.decrypt('AES256', key, iv, data)Decrypt received data
Digital SignatureCrypto.sign('RSA-SHA256', data, privateKey)Sign outbound messages
RandomCrypto.getRandomInteger() / Crypto.generateAesKey(256)Generate tokens, keys

Crypto Key Management

Never hardcode encryption keys in Apex. Use Custom Settings (Protected), Named Credentials, or Platform Encryption’s key management. Keys in code are visible to anyone with “Author Apex” permission and are stored in version control.

Secure Coding Practices

SOQL Injection Prevention

SOQL injection occurs when user input is concatenated directly into SOQL queries.

// VULNERABLE -- user input directly in query string
String userInput = ApexPages.currentPage().getParameters().get('name');
String query = 'SELECT Id, Name FROM Account WHERE Name = \'' + userInput + '\'';
List<Account> accounts = Database.query(query); // SOQL INJECTION RISK
// SAFE -- use bind variables
String userInput = ApexPages.currentPage().getParameters().get('name');
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Name = :userInput];
// SAFE -- use String.escapeSingleQuotes() for dynamic SOQL
String userInput = ApexPages.currentPage().getParameters().get('name');
String safe = String.escapeSingleQuotes(userInput);
String query = 'SELECT Id, Name FROM Account WHERE Name = \'' + safe + '\'';
List<Account> accounts = Database.query(query);

SOQL Injection

Always use bind variables for static SOQL. For dynamic SOQL, always use String.escapeSingleQuotes(). This is a top Salesforce security review finding and will be flagged in any architecture review.

Cross-Site Scripting (XSS) Prevention

XSS occurs when user-supplied data is rendered in HTML without encoding.

ContextPreventionVisualforceLWC
HTML bodyHTML encode{!HTMLENCODE(value)} or <apex:outputText escape="true"/>Automatic (template auto-escapes)
JavaScriptJavaScript encode{!JSENCODE(value)}Use properties, not innerHTML
URL parametersURL encode{!URLENCODE(value)}Use NavigationMixin
CSSCSS encodeAvoid dynamic CSS valuesAvoid dynamic CSS values

Additional Secure Coding Practices

VulnerabilityPrevention
CSRFUse Salesforce anti-CSRF tokens (built into VF/LWC); validate state parameter in OAuth
Open redirectValidate redirect URLs against allowlist; never use user input directly
Insecure direct object referenceAlways check CRUD/FLS/sharing before returning records
Sensitive data exposureUse Shield Encryption; mask fields in UI; avoid logging sensitive data
Insufficient loggingLog security events; use Event Monitoring; implement Transaction Security

Session-Based Permission Sets

Session-based Permission Sets activate only when specific conditions are met during a user’s session. They provide conditional, elevated access.

How Session-Based Permission Sets Work

flowchart LR
    Login["User Logs In"] --> Session["Standard Session\n(Base permissions)"]
    Session --> Trigger["Trigger Event\n(Flow, Auth Provider, Custom)"]
    Trigger --> Activate["Session-Based PS Activated\n(Elevated permissions)"]
    Activate --> Duration["Active until:\n- Session ends\n- Explicitly deactivated\n- Timeout (if configured)"]

    style Session fill:#1565c0,color:#fff
    style Activate fill:#2e7d32,color:#fff

Use Cases for Session-Based Permission Sets

ScenarioImplementation
Step-up authenticationUser re-authenticates to access sensitive data; session PS grants access to encrypted fields
Time-limited elevated accessApproval workflow activates session PS for data export during audit period
Location-based accessFlow checks IP range; activates session PS for internal network users
Role-based escalationManager approves temporary access; Flow activates session PS for the requesting user

Activating Session-Based Permission Sets

MethodHowBest For
FlowSessionPermissionSetActivation elementMost scenarios — declarative
Auth ProviderCustom Auth Provider registration handlerSSO-triggered activation
ApexSessionPermissionSetActivation record insertComplex logic or API-driven
Connected AppSession-based PS assigned to Connected AppIntegration-specific access

Secure Integration Callout Architecture

When designing integrations, the CTA board expects you to articulate the full security stack for callouts — not just “use Named Credentials.”

flowchart TD
    subgraph "Apex Code Layer"
        Controller["Apex Controller\n(with sharing)"]
        Controller --> FLS["Enforce FLS\n(USER_MODE)"]
        FLS --> Callout["HttpRequest via\ncallout:Named_Credential"]
    end

    subgraph "Platform Security Layer"
        NC["Named Credential\n(endpoint URL)"]
        EC["External Credential\n(auth config)"]
        PSAccess["Permission Set\n(controls who can\nuse this credential)"]
    end

    subgraph "Wire Security"
        TLS["TLS 1.2+\n(in transit)"]
        Cert["Mutual TLS\n(certificate auth)"]
    end

    Callout --> NC --> EC
    EC --> PSAccess
    NC --> TLS
    EC --> Cert

    style Controller fill:#2e7d32,color:#fff
    style NC fill:#1565c0,color:#fff
    style EC fill:#1565c0,color:#fff
    style TLS fill:#e65100,color:#fff

Enhanced External Credentials Model

The modern architecture separates concerns: the Named Credential stores the endpoint URL, the External Credential stores the authentication configuration, and a Permission Set controls which users/integrations can use that credential. Multiple Named Credentials can share one External Credential, reducing duplication.

Security in LWC vs Visualforce vs Aura

Security AspectLWCAuraVisualforce
XSS protectionAuto-escaped templatesSemi-auto ({!v.value} is safe, $A.util.format is not)Manual (HTMLENCODE, JSENCODE)
CSRF protectionBuilt-inBuilt-inBuilt-in (anti-CSRF token)
Data accessVia Apex (enforce in Apex)Via Apex (enforce in Apex)Direct binding (enforce FLS in controller)
Namespace isolationShadow DOMLocker ServiceNone
CSP enforcementStrictModerateRelaxed

Sources