Newer Version Available

This content describes an older version of this product. View Latest

Securing Data in Apex Controllers

By default, Apex runs in system mode, which means that it runs with substantially elevated permissions, acting as if the user had most permissions and all field- and object-level access granted. Because these security layers aren’t enforced like they are in the Salesforce UI, you must write code to enforce them. Otherwise, your components may inadvertently expose sensitive data that would normally be hidden from users in the Salesforce UI.

To work with Salesforce records, we recommend using Lightning Data Service, which handles sharing rules, CRUD, and field-level security for you.

Note

Enforce Sharing Rules

When you declare a class, it’s a best practice to specify with sharing to enforce sharing rules when a component uses the Apex controller.

1public with sharing class SharingClass {
2    // Code here
3}

An @AuraEnabled Apex class that doesn’t explicitly set with sharing or without sharing, or is defined with inherited sharing, uses a default or implicit value of with sharing. However, an Apex class that doesn’t explicitly set with sharing or without sharing inherits the value from the context in which it runs. So when a class without explicit sharing behavior is called by a class that sets one of the keywords, it operates with the sharing behavior of the calling class. To ensure that your class enforces sharing rules, set with sharing.

The with sharing keyword enforces record-level security. It doesn’t enforce object-level and field-level security. You must manually enforce object-level and field-level security separately in your Apex classes.

Enforce Object and Field Permissions (CRUD and FLS)

There are a few alternatives to enforce object-level and field-level permissions in your Apex code.

Easiest enforcement using WITH SECURITY_ENFORCED
To enforce object-level and field-level permissions, use the WITH SECURITY_ENFORCED clause for SOQL SELECT queries in Apex code, including subqueries and cross-object relationships.
The WITH SECURITY_ENFORCED clause is ideal if you have minimal experience developing secure code and for applications that don’t require graceful degradation on permissions errors.
This example queries fields on a custom expense object with an insecure method, get_UNSAFE_Expenses(). Don't use this class!
1// This class is an anti-pattern.
2public with sharing class UnsafeExpenseController {
3    // ns refers to namespace; leave out ns__ if not needed
4    // This method is vulnerable because it doesn't enforce FLS. 
5    @AuraEnabled
6    public static List<ns__Expense__c> get_UNSAFE_Expenses() {
7        return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c, 
8            ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
9    } 
10}
This next example uses a secure method, getExpenses(), which uses the WITH SECURITY_ENFORCED clause to enforce object-level and field-level permissions. Use this class instead of UnsafeExpenseController.
1public with sharing class ExpenseController {
2    // This method is recommended because it enforces FLS.
3    @AuraEnabled
4    public static List<ns__Expense__c> getExpenses() {
5    // Query the object safely
6    return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c, 
7            ns__Reimbursed__c, CreatedDate
8             FROM ns__Expense__c WITH SECURITY_ENFORCED];
9    } 
10}
For more details, see the Apex Developer Guide.
Graceful degradation with stripInaccessible()
For more graceful degradation on permissions errors, use the stripInaccessible() method to enforce field- and object-level data protection. This method strips the fields and relationship fields from query and subquery results that the user can’t access. You can find out if any fields were stripped and throw an AuraHandledException with a custom error message, if desired.
You can also use the method to remove inaccessible sObject fields before DML operations to avoid exceptions and to sanitize sObjects that have been deserialized from an untrusted source.
This example updates ExpenseController to use stripInaccessible() instead of the WITH SECURITY_ENFORCED SOQL clause. The results are the same but stripInaccessible() gives you the opportunity to gracefully degrade instead of failing on an access violation when using WITH SECURITY_ENFORCED.
1public with sharing class ExpenseControllerStripped {
2    
3    @AuraEnabled
4    public static List<ns__Expense__c> getExpenses() {
5        // Query the object but don't use WITH SECURITY_ENFORCED 
6        List<ns__Expense__c> expenses = 
7            [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c, 
8                ns__Reimbursed__c, CreatedDate
9                 FROM ns__Expense__c];
10                 
11         // Strip fields that are not readable
12         SObjectAccessDecision decision = Security.stripInaccessible(
13               AccessType.READABLE,
14               expenses);
15               
16         // Throw an exception if any data was stripped
17         if (!decision.getModifiedIndexes().isEmpty()) {
18            throw new AuraHandledException('Data was stripped');
19         }
20         
21         return expenses;
22    } 
23}
For more details and examples, see the Apex Developer Guide.
Legacy code using DescribeSObjectResult and DescribeFieldResult methods
Before the WITH SECURITY_ENFORCED clause and stripInaccessible() method were available, the only way to enforce object and field permissions was to check the current user’s access permission levels by calling the Schema.DescribeSObjectResult and Schema.DescribeFieldResult methods. Then, if a user has the necessary permissions, perform a specific DML operation or a query.
For example, you can call the isAccessible, isCreateable, or isUpdateable methods of Schema.DescribeSObjectResult to verify whether the current user has read, create, or update access to an sObject, respectively. Similarly, Schema.DescribeFieldResult exposes access control methods that you can call to check the current user’s read, create, or update access for a field.
This example uses the describe result methods. This approach requires many more lines of boilerplate code so we recommend using the WITH SECURITY_ENFORCED clause or stripInaccessible() method instead.
1public with sharing class ExpenseControllerLegacy {
2    @AuraEnabled
3    public static List<ns__Expense__c> getExpenses() {
4        String [] expenseAccessFields = new String [] {'Id',
5                                                       'Name',
6                                                       'ns__Amount__c',
7                                                       'ns__Client__c',
8                                                       'ns__Date__c',
9                                                       'ns__Reimbursed__c',
10                                                       'CreatedDate'
11                                                       };
12
13
14    // Obtain the field name/token map for the Expense object
15    Map<String,Schema.SObjectField> m = Schema.SObjectType.ns__Expense__c.fields.getMap();
16
17    for (String fieldToCheck : expenseAccessFields) {
18
19        // Call getDescribe to check if the user has access to view field
20        if (!m.get(fieldToCheck).getDescribe().isAccessible()) {
21
22            // Pass error to client
23            throw new System.NoAccessException();
24        }
25    }
26 
27    // Query the object safely
28    return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c, 
29            ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
30    } 
31}