Cross Site Request Forgery Protection

Cross Site Request Forgery (CSRF) is a class of security vulnerabilities that affect the authenticity of requests. This type of attack occurs when a victim is tricked into clicking on a malicious link that is usually sent via email or on a website. This link forces the victim's browser to make a request to another site to execute an unwanted action. This requires the victim to be authenticated on the other site. For example:
  1. Customer logs into banking application to check a balance.
  2. Customer receives email with a link to win some prize.
  3. Customer clicks link.
  4. Customer’s browser actually makes a request to the banking application to perform a transfer of funds from the victim’s bank account to the attacker’s bank account.
  5. Customer might or might not immediately know that this action took place.

An attacker might be able to force a victim to buy unwanted items, ship baskets to another address, or change user passwords.

Salesforce B2C Commerce Protection Against CSRF

The CSRF protection API uses a Synchronizer Token pattern, which generates a token that is inserted into the HTML page sent to a user. When the user submits content from the page, the server is configured to look for and validate that token. If the token fails to validate, the request should be rejected.

The API is exposed via dw.web.CSRFProtection. This class contains three methods:
  • getTokenName() – returns the expected parameter name as a string that maps to a CSRF Token.
  • generateToken() – securely generates a Token string for the current user. Tokens are unique per call to generateToken.
  • validateRequest() – examines a user’s current request to determine if the request contains a valid CSRF Token. A Token is valid if it was generated for this user’s session in the last 60 minutes.

The CSRFProtection class is used for forms, AJAX, or anywhere a developer needs to protect a request made to the B2C Commerce server.

Using CSRF with Controllers

The best practice for CSRF in SGJC controllers is to include it when handling a specific form action. It's action-specific, because not all actions submit data and require CSRF protection. It also provides better performance, by not requiring the package for every possible action for the form. This mechanism is best practice for controllers, as it avoids issues with caching in templates. However, you can still use the template mechanism for CSRF described later in this topic.

To add CSRF for an action, include the CSRFProtection class and call the validateRequest method. If the validation fails, log the customer out and render an error template. An example is available in the Cart.js controller for the addCoupon action.
    formResult = cartForm.handleAction({
        //Add a coupon if a coupon was entered correctly and is active.
        'addCoupon': function (formgroup) {
            var CSRFProtection = require('dw/web/CSRFProtection');

            if (!CSRFProtection.validateRequest()) { //validate the request
                app.getModel('Customer').logout();  //log the customer out
                app.getView().render('csrf/csrffailed'); //render an error template
                return null;
            }

            var status;
            var result = {
                cart: cart,
                EnableCheckout: true,
                dontRedirect: true
            };
Note: You might see CSRF in guards in SGJC. However, this is part of a legacy implementation of CSRF and is no longer functionally useful.

Using CSRF Protection for Forms Without Page Caching

Add the following to the ISML template:
<form action="${URLUtils.*}" method=POST>
        …
        <input type=”hidden” name=”${dw.web.CSRFProtection.getTokenName()}” value=”${dw.web.CSRFProtection.generateToken()}”/>
        <input type=”submit” value=”Send Protected Request”/>
</form>
For pipelines, add a script node to the pipeline that the form action points to. For example, if the form action points to COBilling-UpdateAddressDetails, the first node in that pipeline must validate the CSRF Token, which can be done with a simple script. The script must contain code similar to:
if( !CSRFProtection.validateRequest() ){
        return PIPELET_ERROR;
    }
   return PIPELET_NEXT;

Using CSRF with Cached Forms

In order to make the content cacheable, the server must not place the CSRF token in the ISML template. Instead, an AJAX callback is used to retrieve a CSRF token for the current session, then JavaScript inserts the token into any necessary HTML location.

Create a CSRF Token Generation Endpoint

First create a pipeline or controller called CSRF-TokenGeneration that must be accessed over HTTPS to protect against malicious interception. It accepts a request and returns a JSON object containing the token name and token value. It has the form:
{“csrf_token_name”: ${dw.web.CSRFProtection.getTokenName()}”,
 “csrf_token_value”: ${dw.web.CSRFProtection.generateToken()}”}
Create a JavaScript module that can be used to make the AJAX call and insert the returned token into a form by id, with code similar to the following example:
function getAndInsertCSRFToken(formid){
    $(document).ready(function(){
      $.ajax(
          {     
            url: '${URLUtils.url("CSRF-GetToken")}', 
            context: document.body,
            dataType: 'json',
            success: function(data, status){
                insertCSRFForm(data, formid);
            },
            error: function(xhr, status, error){
                alert('error' + error);
            }
          }
      );
    });    
}
function insertCSRFForm(csrfjson, formid){
    var csrf_name = csrfjson.csrf_token_name;
    var csrf_value = csrfjson.csrf_token_value;
    var form = document.getElementById(formid);
    var inputfield = document.createElement("input");
    inputfield.type = "text";
    inputfield.name = csrf_name;
    inputfield.value = csrf_value;
    var children = form.children;
    form.insertBefore(inputfield, children[children.length-1]);
}

Insert the CSRF token via JavaScript.

Add CSRF Tokens to forms via JavaScript calls.

<script src='${URLUtils.staticURL("csrfhelpers.js")}'></script>
   
    <body onload=” getAndInsertCSRFToken(‘csrf_test_js’)”>
       <form action="${URLUtils.httpContinue()}" id="csrf_test_js">
            <input type="text" name="foo" value="bar"/>
            <input type="submit" value="Dynamic Submit"/> 
        </form>
    </body>
This example loads the ISML page, executes the AJAX call, pulls in a token, and inserts the token into the csrf_test_js form. Because the form is populated with the token at run time, the page contents might be cached.

Best Practice Recommendations for CSRF

Salesforce recommends the following best practices:
  • Only use POST methods over HTTPS.

    By design, the CSRF Protection Framework only examines request content and only requires session information and the CSRF Token to validate. This means that HTTP requests, including GET methods, are allowed to have CSRF Tokens. It should be considered best practice to only use POST methods over HTTPS, because browser and server logs store GET request content and attackers might easily intercept HTTP requests.

  • Return the customer to the home page if validateRequest() returns false.

    The validateRequest() method returns a boolean value indicating validation success or failure. Best practice is to show a message to users indicating that a potentially malicious event was detected and stopped. Your application can log out the user, return the user to the home page, or send a message to an administrator. Salesforce recommends at least returning a user to the home page of the site.

  • Handle CSRF token timeout.

    CSRF Tokens are good for the life of the session, or 60 minutes, whichever comes first. This is important to note, because some users use multiple browser tabs to get work done. If an old tab is left open for more than 60 minutes and contains a CSRF Protected request, that request fails if it's made after the timeout expires.

  • Properly validate and encode all user input.

    CSRF is a defense-in-depth strategy. All CSRF protection schemes can be defeated using Cross Site Scripting (XSS) attacks. Customer developers need to properly validate and encode all user input so that their storefronts are not exposed to CSRF threats. XSS defeats CSRF protections by stealing the token and inserting it into the malicious link. This would validate the request and let it pass through successfully.