Utilize Apex Security Enhancements to Reduce Development Time

Mike Faust
AppExchange and the Salesforce Ecosystem
4 min readAug 17, 2020

Building security into your ISV application is an essential part of providing trust to your customers. It’s so important that there is a security review process that every ISV application must pass to be listed on the AppExchange.

With the Spring ’20 release come two enhancements that drastically reduce the amount of code you have to write while reducing CPU time and heap size: WITH SECURITY_ENFORCED and Security.stripInaccessible().

WITH SECURITY_ENFORCED

The WITH SECURITY_ENFORCED clause enables field and object-level security checks directly in SOQL select queries in Apex Code, including subqueries and cross-object relationships. This is only needed for queries executed in Apex as Apex runs in system mode whereas queries executed via APIs enforce CRUD/FLS access.

The security is checked before the query execution. If any fields or objects referenced in the SOQL SELECT query using WITH SECURITY_ENFORCED are inaccessible to the user, a System.QueryException is thrown, and no data is returned. So you’ll still need to handle the error messaging when a user tries to query a field that they don’t have access to.

try {
List<Account> accountList = [SELECT Id, Name, AccountNumber, Type
FROM Account WHERE Type = ‘Prospect’ WITH SECURITY_ENFORCED ORDER
BY Name];
}
catch (System.QueryException ex){
throw new AuraHandledException(‘Something went wrong ‘ +
ex.getMessage());
}

NOTE: To pass security review, the API version of your Apex classes that reference WITH SECURITY_ENFORCED must be v48.0 or higher.

There are some limitations to how WITH SECURITY_ENFORCED works. You are unable to traverse through polymorphic relationships, except for ownerId, createdById, and LastModifiedById fields. For example, you can reference WhatId in your query but not What.Name. The TYPEOF statements with an else clause are also not supported.

Additionally, only fields and objects referenced in the select or from SOQL clauses are checked, not fields in the where or order by. This means that users can execute filtering criteria against fields they may not be able to see. If there are use cases where you want to check the security access of the fields in a where or order by clause, you should leverage our next option item stripInaccessible.

Security.stripInaccessible()

The Security.stripInaccessible method can be used to strip the fields from the query and subquery results that the user can’t access. This method can also be used to remove inaccessible sObject fields before DML operations to avoid exception and to sanitize sObjects that have been deserialized from an untrusted source.

Similar to executing schema methods like isAccessible or isCreatable, the access checks are based on the field-level permission of the current user in the context of the specified operation (create, read, update, or upsert).

NOTE: You’ll notice that there is not a delete check with stripInaccessible. When deleting a record, you’ll still need to compose your own isDeletable validation check before executing the delete DML statement.

In this example, we are querying a Contact record with reference to Account fields as a user who doesn’t have access to the Contact.Birthday and Account.Type field.

List<Contact> tmpList = [select Id, Name, Email, Birthdate, Phone, AccountId, Account.Name, Account.Type from Contact limit 3];SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, tmpList);

Security.stripInaccessible returns an instance of the SObjectAccessDecision class. This class contains the list of records with the inaccessible fields to the current user removed. The records are returned in the same order they were submitted into the stripInaccessible method. You can use the getRecords method to access the records.

List<Contact> contactList = securityDecision.getRecords();

The getModifiedIndexes method of the SObjectAccessDecision class lets you see what records had fields removed while the getRemovedFields method shows you what fields were removed for each object in the operation.

System.debug('modified indexes '  +  securityDecision.getModifiedIndexes());Debug Output: modified indexes {0}System.debug('removed fields ' + securityDecision.getRemovedFields());Debug Output: Removed fields {Account={Type}, Contact={Birthdate}}

By combining these methods, you can enforce all-or-none access with regard to field level security. In the scenario below, we created a custom method that creates an account record from an untrusted source (e.g. javascript from lightning web component). If any field is stripped from the record, we prevent the insert from occurring.

public static Account createAccount(Account acct) {
SObjectAccessDecision securityDecision =
Security.stripInaccessible(AccessType.CREATABLE, List<Account>
acct);
//All-or-none logic, so we check if any fields were stripped and
//if so, throw an exception
if (!securityDecision.getModifiedIndexes().isEmpty())
{
throw new AuraHandledException(‘Error Insufficient access to
create account’);
}
insert acct;
return acct;
}

Summary

Leveraging the WITH SECURITY_ENFORCED and Security.stripInaccessible() methods drastically reduce the amount of code needed to perform CRUD/FLS checks. By not having to execute Schema sObject describes, you’ll also improve the performance of your data access methods.

Where to learn more

Filter SOQL Queries Using WITH SECURITY_ENFORCED
Enforce Security With the stripInaccessible Method
Security Class

--

--