Security for Salesforce Developers | Salesforce Developers Blog

At Salesforce, trust is our number one value. When you build apps on the Salesforce Platform, rest assured that they reside in a safe environment.

We implement industry security best practices and standards at all layers, from infrastructure to application code. We provide multiple configuration options that allow you to implement an effective data security model, so that data is accessible only by authorized users We also provide built-in protections for common application security vulnerabilities, such as Cross-Site Scripting, SOQL injection, or Cross-Site Request Forgery. On top of all that, we give you tools for securing storage and transmission of secrets that you can use to secure your applications even more.

However, as a Salesforce Developer, there are certain concepts and best practices that you need to know to prevent leaking data or creating dangerous app security vulnerabilities. In this blog post, we’ll give you a quick overview of the security best practices that you need to follow when coding with Apex and with LWC (Note: Visualforce and Aura are out of the scope of this blog post).

Enforcing data security to protect your records

First, let’s talk about data security. Data security prevents unauthorized access. That is, it ensures that each user has access only to the data that they should have access to. In Salesforce, this is implemented through three different layers: object-level security (also known as CRUD: Create, Read, Update, Delete), field-level security (FLS), and record-level security (also known as Sharing).

I like to explain these three layers with the next picture, in which we imagine a database as if it were a spreadsheet. Basically, each layer controls the visibility of a table (tab), column, or record in the database.

These security layers are configured through several tools, such as permission sets, organization wide defaults, or sharing rules. If you’re a solo developer, or someone on a small team, you may configure these yourself. But if you’re on a larger team, it’s likely that you won’t. However, in both cases, you’ll probably write code. Therefore, it’s extremely important that you understand how application code runs in regards to data security, and how to enforce the different data security mechanisms when they’re not enforced by default.

Basically, operations in Salesforce can run in two modes: User Mode, where data security is enforced, and System Mode, where it isn’t. Operations through entry points as the standard UI (standard Salesforce record pages), and standard API endpoints run in User Mode. Components and services that are used to build user interfaces also run in User Mode. These can be record-form Lightning Components or LDS (Lightning Data Service) endpoints.

However, Apex was designed to implement business logic. Many times, business processes need access to all data, independent of the user who is running the process. That’s why we decided to make Apex run in System Mode by default. This gives you flexibility, but at the same time, you have to implement data security checks when the process does not require those data privileges.

The entry points highlighted in the picture are only a portion. You can find a more exhaustive list in this Stackexchange post.

To enforce data security in Apex, you’ll have to check for object, field, and record-level permissions when:

  • You create, update, or delete records with DML operations
  • You read records with SOQL or SOSL

There are different ways in which you can check for object and field-level security in Apex:

  • Using WITH SECURITY_ENFORCED clause in SOQL queries. This clause prevents retrieving objects and fields the user hasn’t got access to, throwing an exception instead:
    public static List<Account> getAccountsWithSecurityEnforced() {
    return [
    SELECT Name, AnnualRevenue, Industry
    FROM Account
    WITH SECURITY_ENFORCED
    ORDER BY Name
    ];
    }
  • Using Schema.DescribeSObjectResult and Schema.DescribeFieldResult methods. They allow great flexibility to fine tune how your code checks for data security, but they also require more work on your part as a developer and occupy more lines of code:
  public static Account createAccountCRUDCheck() {
    Account acct;
    // Just CRUD check
    if (Schema.sObjectType.Account.isCreateable()) {
      acct = new Account();
      acct.Name = 'ACME';
      acct.AnnualRevenue = 1000000;

      insert acct;
    } else {
      throw new DMLException('No object permissions to create account.');
    }

    return acct;
  }
  public static List<Account> getAccountsFLSCheck() {
    // FLS check (CRUD check implicit)
    if (
      Schema.sObjectType.Account.fields.Name.isAccessible() &&
      Schema.sObjectType.Account.fields.AnnualRevenue.isAccessible() &&
      Schema.sObjectType.Account.fields.Industry.isAccessible()
    ) {
      return [SELECT Name, AnnualRevenue, Industry FROM Account ORDER BY Name];
    }
    return new List<Account>();
  }
public static List<Account> getAccountsStripInaccessible() {
    SObjectAccessDecision securityDecision = Security.stripInaccessible(
      AccessType.READABLE,
      [SELECT Name, AnnualRevenue, Industry FROM Account ORDER BY Name]
    );

    System.debug(securityDecision.getRecords());

    return securityDecision.getRecords();
  }
  public static Account createAccountStripInaccessible() {
    Account acct = new Account();
    acct.Name = 'ACME';
    acct.AnnualRevenue = 1000000;

    SObjectAccessDecision securityDecision = Security.stripInaccessible(
      AccessType.CREATABLE,
      new List<Account>{ acct }
    );

    insert securityDecision.getRecords();

    return (Account) securityDecision.getRecords()[0];
  }

There is a fourth option, not generally available yet, but in pilot, called Secure Apex Code with User Mode Database Operations pilot. With this pilot, Database methods will support an AccessLevel parameter that will enable you to run database operations in user mode. Stay tuned!

Bonus: if you want to see a more advanced usage example of Schema methods, take a look at CanTheUser in Apex Recipes. This class is a library that exposes easy-to-read methods to check for object and field level security.

To enforce record-level security, you need to indicate a sharing clause in the definition of the Apex class. A class can be with sharing (enforces record permissions), without sharing (intentionally skips record permissions) or inherited sharing (inherits record security check behavior from calling code).

public with sharing class AccountController {
   // Class contents
}

Not specifying a sharing clause is always a bad practice. Omitting the sharing clause from a class and using inherited sharing both enforce the sharing behavior of the calling class. However, if the class is the entry point into the Apex transaction, inherited sharing defaults to the more secure with sharing while the omitted case defaults to without sharing. In addition, if the sharing clause is omitted, it’ll be unclear if the inherited behavior is expected or if the developer just forgot to indicate it.

As you’ve seen, the ways to check for object and field-level security are different from how you check for record-level security. This is a common misconception that I would love readers to remember: Setting a class as with sharing does not enforce object or field-level security permissions!

Preventing threats with application security

Application Security describes best practices to follow when building apps in order to prevent common threats and vulnerabilities. Yearly, the Open World-Wide Application Security Project (OWASP) organization revises the OWASP Top 10 list. This a report that outlines the 10 most critical vulnerabilities that are affecting web application security. These are the vulnerabilities listed in OWASP Top 10 for 2021:

A1:2017-Injection
A2:2017-Broken Authentication
A3:2017-Sensitive Data Exposure
A4:2017-XML External Entities (XXE)
A5:2017-Broken Access Control
A6:2017-Security Misconfiguration
A7:2017-Cross-Site Scripting XSS
A8:2017-Insecure Deserialization
A9:2017-Using Components with Known Vulnerabilities
A10:2017-Insufficient Logging & Monitoring

Luckily, Salesforce has default protections in place for most of these vulnerabilities, such as CSP, Locker Service, Shadow DOM, and many more. However, there are a few edge cases, in which a developer can unintentionally open the door to some of them. Let’s discuss some of these situations:

SOQL injection

SQL injection occurs when untrusted data is interpreted as part of a SQL query. In Salesforce, we have our own query language, SOQL. SOQL is similar to SQL, but one of their main differences is that SOQL cannot be used to modify data, only to read. As a consequence, SOQL is inherently more secure against SOQL injection attacks, as they won’t modify the database. However, an SOQL injection attack may reveal sensitive data that the developer wasn’t expecting to expose.

Using static SOQL query syntax in Apex mitigates the risk of a SOQL injection attack. Static query tokens are validated at compile time, and Apex variables bound with “:” are treated as variables at runtime to ensure that they can’t become executable elements in the query.

public static List<Account> getFilteredAccountsStatic(String searchValue) {
    String likeValue = '%' + searchValue + '%';
    // SECURE QUERY :)
    return [
      SELECT Name, AnnualRevenue, Industry
      FROM Account
      WHERE Name LIKE :likeValue
      ORDER BY Name
    ];
  }

However, if you use dynamic queries, SOQL injection may happen. Think about the following code:

  public static List<Account> getFilteredAccountsInjection(String searchValue) {
    String likeValue = '\'%' + searchValue + '%\'';
    // DON'T DO THIS!!!
    return (List<Account>) Database.query(
      'SELECT Name, AnnualRevenue, Industry FROM Account WHERE Name LIKE ' +
      likeValue +
      ' ORDER BY Name'
    );
  }

If an attacker gets to pass in a value like %' AND AnnualRevenue >= 2000000 AND Industry LIKE '%, to the searchValue string, they will be able to retrieve all accounts which AnnualRevenue >=2000000, revealing private information.

To prevent this:

  • Use static queries when possible
  • If using dynamic queries, bind untrusted data with “:”
public static List<Account> getFilteredAccountsBinding(String searchValue) {
    String likeValue = '%' + searchValue + '%';
    return (List<Account>) Database.query(
      'SELECT Name, AnnualRevenue, Industry FROM Account WHERE Name LIKE :likeValue ORDER BY Name'
    );
  }
  • If any of these options is possible, escape, typecast (find an example here) or allowlist (to allow only identified values) the untrusted data
public static List<Account> getFilteredAccountsEscape(String searchValue) {
    String likeValue = '\'%' + String.escapeSingleQuotes(searchValue) + '%\'';
    return (List<Account>) Database.query(
      'SELECT Name, AnnualRevenue, Industry FROM Account WHERE Name LIKE ' +
      likeValue +
      ' ORDER BY Name'
    );
  }

Cross-Site Scripting (XSS)

With Cross-Site Scripting (XSS), attackers get to inject malicious scripts in the victim’s browser. This happens whenever an application includes untrusted data in the output it generates without validating or encoding it. XSS can lead into session hijacking, malicious redirects, or simply deface the web app.

Locker Service and CSP (Content-Security Policy) are our tools to fight against XSS in Salesforce. However, even though they do a great job, there are some development best practices that need to be followed to not decrease its efficacy:

  • Stay away from DOM manipulation (lwc:dom="manual") when possible, and use template directives instead. Why? because when you let the framework manipulate the DOM, Locker Service sanitizes the inputs before rendering the HTML. While if you manipulate it yourself, Locker Service doesn’t do it. Alternatively, if you need to use lwc:dom="manual", sanitize the inputs yourself. You can do that by applying input filtering (e.g. don’t allow >) or output encoding (e.g. transform > to &lt;).
  • Avoid JavaScript functions that evaluate strings as code, as Eval, DOMParser.parseFromString, Document.implementation.createHTMLDocument and setTimeout / setInterval (when they’re used to evaluate a string).
  • Properly evaluate third-party libraries, as they may contain XSS vulnerabilities too! You can do it manually or by using automatic tools, such as CheckMarx or Snyk.

Cross-Site Request Forgery (CSRF)

Cross-Site Request Forgery (CSRF) forces a user to execute unwanted actions on a web app in which they’re authenticated. Imagine that the user is authenticated with Salesforce. With CSRF, a malicious attacker could make the user delete accounts, change an opportunity amount, or something much worse. And the logged in user may not be aware that it is occurring! In some cases, a CSRF attack doesn’t modify the database, such as logging the user out. While not immediately harmful, it can be bothering.

Salesforce has CSRF protections in place; this protection is activated (and cannot be deactivated by default) in the Setup menu. The protection sends a token with every request that the server validates before responding. This makes the attacker’s life harder, as he or she would have to find out both the URL and the anti-CSRF token.

However, again, there is an edge case that the protection doesn’t cover. This is the exact moment in which a page loads. The recommendation in this case is to avoid changing the database state on page load. That is, don’t perform DML operations in Lightning Web Components load (constructor, connectedCallback, renderedCallback).

Sensitive data exposure

Sometimes, you need to work with sensitive data in your code, such as passwords, encryption keys, OAuth tokens, payment information, etc. This kind of data should never be hardcoded, but instead properly stored in applications. In Salesforce, there are several features that you can use to store secrets securely:

  • Named Credentials: a type of metadata used to store information about callout endpoints and that can be easily referenced in Apex. They allow you to set up authentication and maintain authentication-related secrets securely stored in Salesforce.
  • Protected Custom Settings: custom settings that belonging to a package are not visible in subscriber’s orgs.
  • Protected Custom Metadata and Protected Custom Metadata Records: custom metadata types that belonging to a package are not visible for subscribers. You also have the option to protect individual custom metadata records instead of the custom metadata type.
  • Shield Platform Encryption: a feature that allows you to encrypt data at rest (in the database) for stricter security compliance requirements. As a developer, know that when using shield encrypted fields, there are some SOQL and SOSL limitations.

Additionally, if you need to send sensitive information outside the security boundaries of Salesforce, you can encrypt your data and decrypt it at the destination. There are several functions in the Apex Crypto class that will help you implement different algorithms to ensure secrecy, integrity, authenticity and no-repudiation of data. Equivalently, you can find JavaScript libraries as CryptoJS with the same purpose and similar utilities.

Next steps

In this blog post we have covered some of the security best practices that you should follow when developing on the Salesforce Platform. This is not a complete list, as security is a big topic, but it’s a good starting point!

To explain these concepts in more depth, I have created a video series. The series contains three one-hour episodes that I hope you enjoy. Watch the series here:

If you want to get hands on, I recommend that you to take a look at the Develop Secure Web Apps trail on Trailhead. If you want to check the complete documentation, take a look at the Salesforce Security Guide.

About the author

Alba Rivas works as a Lead Developer Evangelist at Salesforce. She focuses on Lightning Web Components and Lightning adoption strategy. You can follow her on Twitter @AlbaSFDC (http://twitter.com/AlbaSFDC).

Stay up to date with the latest news from the Salesforce Developers Blog

Subscribe