Apex Security and Sharing

Introduction

When it comes to security in Salesforce, there are two main areas to consider: field/object security and record access. Field and object security are managed through Field-Level Security (FLS), while record access is controlled through sharing settings.

Apex provides several ways to enforce FLS and sharing rules. You can use user mode, system mode, with sharing, without sharing, WITH SECURITY_ENFORCED, stripInaccessible, and more. With so many options available, it can be difficult to understand which approach to use in different scenarios within your Apex code.

In this post, I’m going to make it simple and practical. I won’t label any approach as “good” or “bad.” Instead, I’ll show you when to use each security feature and how to apply it effectively.

Basics

As mentioned in the introduction, there are two main areas to consider: field/object security and record access. Let’s go through each of them.

Field-Level Security (FLS)

Field-Level Security (FLS) controls access to objects and fields. This access is granted through Profiles and Permission Sets. Users need these permissions to view objects and fields in the Salesforce UI, for example on a record page.

However, when it comes to Apex, this access is not enforced automatically. That does not mean we can ignore it, but we’ll come back to that later.

Apex runs in system context, which means field-level and object-level permissions are not automatically respected. In practice, this means that unless you explicitly enforce FLS in your Apex code, the running user will still be able to execute the code and access data that they would not normally be able to see in the UI.

public without sharing class MyController {
    @AuraEnabled
    public static List<Account> getAccounts() {
        return [
            SELECT Id, Name, Industry, BillingCity, Phone 
            FROM Account
            LIMIT 2000
        ];
    }
}

Let’s assume that the user executing the code above does not have access to the Industry, BillingCity, and Phone fields. Nevertheless, the query will still return those values, because, once again, Apex runs in system context.

The same rule applies to DML operations.

public without sharing class MyController {
    @AuraEnabled
    public static void createAccount() {
        insert new Account(
            Name = 'Beyond the Cloud',
            Industry = 'IT',
            Phone = '123456789',
            BillingCity = 'Warsaw'
        );
    }
}

Even if the running user does not have access to the Industry, BillingCity, and Phone fields, the Account record will still be created and all of those fields will be populated.

Important!

So, what should we do? Should we always enforce FLS? Not necessarily.

It depends on the use case. In my opinion, most automation should run in system mode. However, as a developer, it is still your responsibility to make sure users do not gain access to objects or fields that contain sensitive data.

In the next part of this article, we’ll look at how to enforce FLS in Apex.

Sharings

Sharing is about access to data represented by records stored in your Salesforce org. In simple terms, it defines which records a user can see.

Sharing is a bit more complex than FLS because record access can be granted through many different mechanisms, including:

  • organization-wide defaults (OWD)
  • role hierarchy
  • sharing rules
  • manual sharing
  • apex managed sharing
  • team access
  • territory hierarchy access
  • profile and permission sets (!!)

Earlier, I mentioned that Profiles and Permission Sets are responsible for FLS. While that is true, record access can also be granted through powerful system permissions such as View All Data, Modify All Data, View All Records, or Modify All Records. In practice, this approach should be used sparingly and usually limited to administrators.

Sharing in Apex works differently than FLS. An Apex class "has to" specify its sharing mode at the top of the class using one of the following keywords:

  • without sharing
  • with sharing
  • inherited sharing

I said an Apex class “has to” specify a sharing mode, but that is not entirely true. Technically, you can omit it. However, that is not recommended.

Apex without an explicit sharing declaration is insecure by default. We strongly recommend that you always specify a sharing declaration for a class. ~ Salesforce

When no sharing mode is specified, the behavior depends on what invoked the Apex code. Salesforce documents those details here.

How each sharing keyword works is something we’ll cover later.

Enforce FLS

As you already know, Apex runs in system context. That means it is our responsibility to enforce FLS in Apex code.

There are several ways to do that:

  • Schema
  • Security.stripInaccessible
  • WITH SECURITY_ENFORCED
  • USER_MODE

    Schema

Applies to: DML and SOQL

One way to verify access to objects and fields in Apex is by using Schema. Schema can be used to check whether an object is creatable, accessible, updateable, deletable, or undeletable. You can find the full list of available methods in DescribeSObjectResult. It can also be used to check access to individual fields. The full list of field-related methods is available in DescribeFieldResult.

Note!

Using Schema to enforce FLS is no longer considered the most efficient approach. It was commonly used a few years ago, but today there are simpler and more recommended alternatives. Personally, I don’t see many strong use cases for it in modern Apex code.

Access to an object:

Boolean isCreateable = Schema.sObjectType.Contact.isCreateable();
Boolean isAccessible = Schema.sObjectType.Contact.isAccessible();
Boolean isUpdateable = Schema.sObjectType.Contact.isUpdateable();
Boolean isDeletable = Schema.sObjectType.Contact.isDeletable();

Access to fields:

Boolean isEmailCreatable = Schema.sObjectType.Contact.fields.Email.isCreateable();
Boolean isEmailAccessible = Schema.sObjectType.Contact.fields.Email.isAccessible();
Boolean isEmailUpdatable = Schema.sObjectType.Contact.fields.Email.isUpdateable();

Schema can be used with both SOQL and DML.

DML

if (Schema.sObjectType.Contact.isCreateable()) {
    insert new Contact(LastName = 'Smith');
}
if (Schema.sObjectType.Contact.isUpdateable()) {
    update contact;
}
if (Schema.sObjectType.Contact.isDeletable()) {
    delete contact;
}

SOQL

if (Schema.sObjectType.Contact.fields.Email.isAccessible()) {
    Contact record = [SELECT Email FROM Contact LIMIT 1];
}

Security.stripInaccessible

Applies to: DMLs and SOQLs

Security.stripInaccessible removes fields from the provided source that are not accessible to the user, based on the specified AccessType. It can be useful in several scenarios. For example, you can use it to remove inaccessible fields from query results before displaying them in the UI. You can also use it before a DML operation to prevent errors caused by insufficient field access. Another strong use case is sanitizing records coming from an untrusted source.

As stated in the documentation:

Use the stripInaccessible method to enforce field-level and object-level data protection. […] The method can also be used to remove inaccessible sObject fields before DML operations to avoid exceptions and to sanitize sObjects that have been deserialized from an untrusted source. ~ Salesforce

SObjectAccessDecision strippedAccounts = 
    Security.stripInaccessible(AccessType.READABLE, accounts);

Account account = strippedAccounts.getRecords()[0];

DML

List<Account> accounts = new List<Account>{
    new Account(Name = 'Acme Corporation'),
    new Account(Name = 'Blaze Comics', Rating = ’Warm’)
};

SObjectAccessDecision securityDecision = 
    Security.stripInaccessible(AccessType.CREATABLE, accounts);

// No exceptions are thrown and no rating is set
insert securityDecision.getRecords();

SOQL

SObjectAccessDecision strippedAccounts = 
    Security.stripInaccessible(
        AccessType.READABLE,
        [SELECT Name, Industry, Website FROM Account]                 
    );

SECURITY_ENFORCED

Applies to: SOQLs

WITH SECURITY_ENFORCED is another way to enforce field- and object-level security permissions, but it applies only to SOQL queries. Salesforce does not recommend using WITH SECURITY_ENFORCED. However, that does not mean it has no valid use cases.

As stated in the documentation:

Use the WITH SECURITY_ENFORCED clause to enable field- and object-level security permissions checking for SOQL SELECT queries in Apex code, including subqueries and cross-object relationships. ~ Salesforce

USER_MODE

Applies to: DMLs and SOQLs

User mode is the newest and recommended way to enforce FLS for both SOQL and DML operations. It addresses the gaps that WITH SECURITY_ENFORCED had. The biggest downside is that both FLS and sharing rules are enforced when user mode is used. There is no way to, for example, enforce only FLS without also enforcing sharing.

As stated in the documentation:

 Field-level security (FLS) and object permissions of the running user are respected in user mode, […]. User mode always applies sharing rules. ~ Salesforce

DML

insert as user new Account(Name = 'Test');

SOQL

List<Account> accounts = [SELECT Id FROM Account WITH USER_MODE];

Enforce Sharing

There are a few ways to enforce sharing in Apex: by using a class sharing keyword or USER_MODE. Let’s dive into it.

Class Sharing Keywords

Important!

Class sharing keywords apply only in system mode. When user mode is used (WITH USER MODE, as user), sharing is always enforced, regardless of which class sharing keyword is specified.

without sharing

When a class is declared without sharing, SOQL queries can return all records, even if the user executing the code would not normally have access to those records through OWD, role hierarchy, sharing rules, or other sharing mechanisms.

public without sharing class MyController {
    @AuraEnabled
    public static List<Account> getAccounts() {
        return [
            SELECT Id, Name, Industry, BillingCity, Phone 
            FROM Account
            LIMIT 2000
        ];
    }
}

with sharing

When class has with sharing keyword – SOQL will return ONLY the data, to which user who executed the code have access to data granted via owd, role hierarchy, etc.

public with sharing class MyController {
    @AuraEnabled
    public static List<Account> getAccounts() {
        return [
            SELECT Id, Name, Industry, BillingCity, Phone 
            FROM Account
            LIMIT 2000
        ];
    }
}

inherited sharing

`
The inherited sharing keyword as the name suggests, will get sharing mode from the class that executed the code:

public with sharing class MyController {
    public class with sharing WithSharingExecutor {
        public List<Account> getAccounts() {
            return new MyController.InheritedSharingExecutor.getAccounts();
        }
    }

    public class without sharing WithSharingExecutor {
        public List<Account> getAccounts() {
            return new MyController.InheritedSharingExecutor.getAccounts();
        }
    }

    public class inherited sharing InheritedSharingExecutor {
        public List<Account> getAccounts() {
            return [
                SELECT Id, Name, Industry, BillingCity, Phone 
                FROM Account
                LIMIT 2000
            ];
        }
    }
}

The getAccounts method from WithSharingExecutor will execute query with sharing, the getAccounts method from WithSharingExecutor will execute query without sharing.

USER_MODE

User mode is the newest and recommended way to enforce FLS for both SOQL queries and DML operations. It addresses the gaps that WITH SECURITY_ENFORCED had.

The biggest downside is that both FLS and sharing rules are enforced when user mode is used. There is no way to, for example, enforce only FLS without also enforcing sharing.

Once again: sharing is enforced regardless of which keyword is specified at the class level.

 Field-level security (FLS) and object permissions of the running user are respected in user mode, […]. User mode always applies sharing rules. ~ Salesforce

DML

insert as user new Account(Name = 'Test');

SOQL

List<Account> accounts = [SELECT Id FROM Account WITH USER_MODE];

Combinations

Now that we know the different ways to enforce field/object security and sharing rules in Apex, we can look at how these mechanisms relate to one another, how they complement each other, when to use each of them, and what consequences come with each approach.

How should you read these tables?

The FLS Result and Sharing Result columns represent the outcome of combining the three earlier columns. A - means that a given mechanism is not used.

SOQL

  • Schema – There is no reason to use schema. Use rather Security.stripInaccessible, WITH SECURITY_ENFORCE or USER_MODE.
SOQL Keyword Class sharing keyword stripInaccessible FLS Result Sharing Result
with sharing
without sharing
inherited sharing 🔄
WITH SYSTEM_MODE with sharing
WITH SYSTEM_MODE without sharing
WITH SYSTEM_MODE inherited sharing 🔄
with sharing Used ✅
without sharing Used ✅
inherited sharing Used ✅ 🔄
WITH SYSTEM_MODE with sharing Used ✅
WITH SYSTEM_MODE without sharing Used ✅
WITH SYSTEM_MODE inherited sharing Used ✅ 🔄
WITH USER_MODE with sharing
WITH USER_MODE without sharing
WITH USER_MODE inherited sharing
WITH SECURITY_ENFORCED with sharing
WITH SECURITY_ENFORCED without sharing
WITH SECURITY_ENFORCED inherited sharing 🔄

Salesforce recommends using WITH USER_MODE. However, as I already mentioned, user mode enforces both FLS and sharing.

If you want to enforce FLS but have sharing determined by the class sharing keyword, you need to use WITH SECURITY_ENFORCED or Security.stripInaccessible.

For this scenario, I would generally recommend WITH SECURITY_ENFORCED. However, for dynamic SOQL, Security.stripInaccessible can make more sense.

DML

DML Keyword Class sharing keyword stripInaccessible FLS Result Sharing Result
with sharing ❌  ✅ 
without sharing ❌  ❌ 
inherited sharing ❌  🔄 
as system with sharing ✅ 
as system without sharing ❌  ❌ 
as system inherited sharing ❌  🔄
with sharing Used ✅ ✅ 
without sharing Used ✅ ❌ 
inherited sharing Used ✅ 🔄 
as system with sharing Used ✅ ✅ 
as system without sharing Used ✅ ❌ 
as system inherited sharing Used ✅ ✅   🔄
as user with sharing ✅ 
as user without sharing ✅  ✅ 
as user inherited sharing ✅  ✅ 

Salesforce recommends using as user. However, as I already mentioned several times, user mode enforces both FLS and sharing.

If you want to enforce FLS while keeping sharing behavior determined by the class sharing keyword, you need to use Security.stripInaccessible together with system mode.

Github

Check the FLS test on GitHub.


Note: This post was not written by AI. I only used AI to help refine my grammar because I am not a native speaker.


Resources

Piotr Gajek
Piotr Gajek
Senior Salesforce Developer
Technical Architect and Full-stack Salesforce Developer. He started his adventure with Salesforce in 2017. Clean code lover and thoughtful solutions enthusiast.

You might also like

Abstract, Virtual, Interface in Apex
August 14, 2022

Abstract, Virtual, Interface in Apex

Find the differences between Abstract, Virtual, and Interface implementation in Apex code. Understand the purpose and make your code better.

Piotr Gajek
Piotr Gajek

Senior Salesforce Developer