Security in Apex

Visualforce {!field} bindings and LWC/Aura base components with lightning-record-form enforce FLS automatically, but data returned from Apex does not — you must enforce it.

29.1 The Salesforce security layers

Enforced in Apex by default?

Object permissions (CRUD) Can the user create/read/edit/delete this object?

No (system mode) Field-level security (FLS) Can the user see/edit this field?

Which records can the user see/edit?

Depends on the sharing keyword e.g., *Modify All Data*, *View Setup*

Check with FeatureManagement.checkPermission for custom permissions

Visualforce {!field} bindings and LWC/Aura base components with lightning-record-form enforce FLS automatically, but data returned from Apex does not — you must enforce it.

29.2 User mode (recommended)

User mode enforces CRUD, FLS, and sharing; violations throw exceptions (for queries, QueryException listing inaccessible fields; for DML, DmlException/SecurityException). There is also a pattern to run with an additional permission set's access (AccessLevel.USER_MODE.withPermissionSetId(psId)) — this was introduced as a beta/developer preview, so check the current release notes before relying on it.

// queries
List<Contact> cons = [SELECT Id, Name, Email FROM Contact WITH USER_MODE];
List<SObject> dyn = Database.query('SELECT Id FROM Lead', AccessLevel.USER_MODE);

// DML
insert as user newContacts;
update as user changedContacts;
Database.SaveResult[] r = Database.update(changedContacts, false, AccessLevel.USER_MODE);

// SOSL
List<List<SObject>> s = [FIND 'Acme' IN ALL FIELDS RETURNING Account(Name) WITH USER_MODE];

29.3 Security.stripInaccessible

Instead of failing, silently remove fields the user cannot access:

AccessType values: READABLE, CREATABLE, UPDATABLE, UPSERTABLE. Note: after stripping, accessing a removed field on the returned records throws SObjectException — check with record.isSet('Field') or getPopulatedFieldsAsMap().

List<Account> raw = [SELECT Id, Name, AnnualRevenue, Secret_Score__c FROM Account LIMIT 50];
SObjectAccessDecision decision = Security.stripInaccessible(AccessType.READABLE, raw);
List<Account> safe = (List<Account>) decision.getRecords();
Map<String, Set<String>> removed = decision.getRemovedFields();   // e.g., {Account={Secret_Score__c}}

// before DML: strip fields the user may not set
List<Account> incoming = (List<Account>) JSON.deserialize(payload, List<Account>.class);
SObjectAccessDecision d2 = Security.stripInaccessible(AccessType.CREATABLE, incoming);
insert d2.getRecords();

29.4 Describe-based checks (classic approach)

if (!Schema.sObjectType.Contact.isAccessible() || !Schema.sObjectType.Contact.fields.Email.isAccessible()) {
    throw new AuraHandledException('Insufficient access to Contact email');
}
if (Schema.sObjectType.Contact.isUpdateable() && Schema.sObjectType.Contact.fields.Phone.isUpdateable()) {
    update contacts;
}
Boolean canDelete = Account.SObjectType.getDescribe().isDeletable();
Boolean hasCustomPerm = FeatureManagement.checkPermission('Approve_Discounts');

29.5 Choosing an enforcement method

WITH USER_MODE / as user CRUD + FLS + sharing Default for user-facing code Removes fields silently

Partial data is acceptable (lists, wrappers) WITH SECURITY_ENFORCED CRUD + FLS on SELECT fields Whatever you check Fine-grained or custom messages with sharing Always pair with CRUD/FLS enforcement

29.6 Other secure-coding topics

SOQL/SOSL injection: binds, escapeSingleQuotes, allowlists (§15.2).

Cross-site scripting (XSS) in Visualforce: avoid escape="false"; use JSENCODE, HTMLENCODE, URLENCODE. LWC auto-escapes template expressions; never assign untrusted HTML to innerHTML (and Lightning Locker / Lightning Web Security restrict DOM access).

CSRF: Visualforce/Lightning include protection; never perform DML in Visualforce page constructors or action attributes on page load.

Secrets: never hard-code credentials or API keys; use Named/External Credentials or protected custom metadata/settings in managed packages.

Open redirects: validate retURL-style parameters.

Sensitive data in logs: don't System.debug passwords, tokens, or PII.

Crypto: use the Crypto class (Crypto.generateAesKey(256), Crypto.encryptWithManagedIV, Crypto.generateDigest('SHA-256', blob), Crypto.generateMac('hmacSHA256', data, key)).

Tools: Salesforce Code Analyzer (PMD + security rules + Graph Engine), Checkmarx-based source scanner (required for AppExchange security review).