Writing secure Apex code is critical. This guide covers three essential security mechanisms: sharing, Field-Level Security (FLS), and CRUD checks.
The with sharing keyword enforces Salesforce's sharing model. The without sharing keyword bypasses it.
public without sharing class AccountService {
// This class can see ALL accounts regardless of user's sharing settings
public static List<Account> getAllAccounts() {
return [SELECT Id, Name FROM Account]; // User sees all accounts!
}
}
public with sharing class AccountService {
// This class respects the user's sharing settings
public static List<Account> getMyAccounts() {
// User only sees accounts they own or have been shared with
return [SELECT Id, Name FROM Account];
}
}
Only use without sharing when you have a specific business reason and understand the security implications:
public without sharing class AdminUtility {
// Admin utility that needs to process all records regardless of sharing
public static void updateAllRecordsStatus() {
// Carefully validate that this should bypass sharing
List<Account> allAccounts = [SELECT Id FROM Account];
// ... business logic
}
}
Always check if a user has access to a field before reading or writing it.
public class AccountProcessor {
public static void updateAccounts(List<Account> accounts) {
for (Account acc : accounts) {
acc.Sensitive_Data__c = 'secret value'; // No permission check!
update acc;
}
}
}
public with sharing class AccountProcessor {
public static Boolean userCanUpdateField(String objectName, String fieldName) {
// Check if field is accessible
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
DescribeSObjectResult objDescribe = objType.getDescribe();
DescribeFieldResult fieldDescribe = objDescribe.fields.getMap()
.get(fieldName)
.getDescribe();
// Check create/update permission
return fieldDescribe.isUpdateable();
}
public static void updateAccounts(List<Account> accounts) {
// Check FLS before accessing field
if (!userCanUpdateField('Account', 'Sensitive_Data__c')) {
throw new SecurityException('You do not have permission to update this field');
}
for (Account acc : accounts) {
acc.Sensitive_Data__c = 'secret value';
update acc;
}
}
}
public class SecurityException extends Exception { }
Always check if a user can perform CRUD operations before doing DML.
public class AccountService {
public static void deleteAllAccounts(List<Id> accountIds) {
List<Account> accounts = [SELECT Id FROM Account WHERE Id IN :accountIds];
delete accounts; // May fail if user doesn't have delete permission!
}
}
public with sharing class AccountService {
public static Boolean userCanDelete(String objectName) {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
DescribeSObjectResult objDescribe = objType.getDescribe();
return objDescribe.isDeletable();
}
public static Boolean userCanCreate(String objectName) {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
DescribeSObjectResult objDescribe = objType.getDescribe();
return objDescribe.isCreateable();
}
public static Boolean userCanUpdate(String objectName) {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
DescribeSObjectResult objDescribe = objType.getDescribe();
return objDescribe.isUpdateable();
}
public static void deleteAccounts(List<Id> accountIds) {
if (!userCanDelete('Account')) {
throw new SecurityException('You do not have permission to delete accounts');
}
List<Account> accounts = [SELECT Id FROM Account WHERE Id IN :accountIds];
delete accounts;
}
public static void createAccount(Account acc) {
if (!userCanCreate('Account')) {
throw new SecurityException('You do not have permission to create accounts');
}
insert acc;
}
}
public class SecurityException extends Exception { }
public with sharing class SecurityUtil {
// Check if user can read object
public static Boolean canReadObject(String objectName) {
try {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
return objType.getDescribe().isAccessible();
} catch (Exception e) {
return false;
}
}
// Check if user can create object
public static Boolean canCreateObject(String objectName) {
try {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
return objType.getDescribe().isCreateable();
} catch (Exception e) {
return false;
}
}
// Check if user can update object
public static Boolean canUpdateObject(String objectName) {
try {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
return objType.getDescribe().isUpdateable();
} catch (Exception e) {
return false;
}
}
// Check if user can delete object
public static Boolean canDeleteObject(String objectName) {
try {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
return objType.getDescribe().isDeletable();
} catch (Exception e) {
return false;
}
}
// Check if user can read field
public static Boolean canReadField(String objectName, String fieldName) {
try {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
DescribeSObjectResult objDescribe = objType.getDescribe();
DescribeFieldResult fieldDescribe = objDescribe.fields.getMap()
.get(fieldName)
.getDescribe();
return fieldDescribe.isAccessible();
} catch (Exception e) {
return false;
}
}
// Check if user can write field
public static Boolean canWriteField(String objectName, String fieldName) {
try {
SObjectType objType = Schema.getGlobalDescribe().get(objectName);
DescribeSObjectResult objDescribe = objType.getDescribe();
DescribeFieldResult fieldDescribe = objDescribe.fields.getMap()
.get(fieldName)
.getDescribe();
return fieldDescribe.isUpdateable();
} catch (Exception e) {
return false;
}
}
}
// Usage in your code
public with sharing class SafeAccountService {
public static void processAccounts(List<Account> accounts) {
// Check object-level permission
if (!SecurityUtil.canUpdateObject('Account')) {
throw new SecurityException('No update permission on Account');
}
// Check field-level permission
if (!SecurityUtil.canWriteField('Account', 'Description')) {
throw new SecurityException('No permission to update Account.Description field');
}
for (Account acc : accounts) {
acc.Description = 'Updated';
}
update accounts;
}
}
// ❌ Vulnerable to injection
String searchTerm = 'Acme'; // Could be malicious input
List<Account> accounts = [SELECT Id FROM Account WHERE Name = :searchTerm];
// ✅ Safe - uses bind variables
String searchTerm = 'Acme';
List<Account> accounts = [SELECT Id FROM Account WHERE Name = :searchTerm];
public with sharing class InputValidator {
public static String sanitizeInput(String input) {
if (String.isBlank(input)) {
return null;
}
// Remove potentially dangerous characters
return input.replaceAll('[^a-zA-Z0-9 ]', '');
}
public static void createAccount(String name) {
String safeName = sanitizeInput(name);
if (String.isBlank(safeName)) {
throw new ValidationException('Invalid account name');
}
Account acc = new Account(Name = safeName);
insert acc;
}
}
public class ValidationException extends Exception { }
with sharing on all public classes by defaultwithout sharing is neededwith sharing to respect Salesforce's sharing modelwithout sharing when absolutely necessary