Configuring security against Cross-Site Request Forgery (CSRF)

Because Content Services GraphQL API is a stateless application, you must configure a token exchange for your applications to protect against Cross-Site Request Forgery (CSRF) attacks.

Cross-Site Request Forgery (CSRF/XSRF) is an attack that forces an end user to run unwanted actions on a web application where they are currently authenticated. In such an attack, if a user tries to access their intended application, the malicious code can inject commands to make the application do what the malicious code wants. This malicious code might be running in another tab or window of the browser.

CSRF attacks are common attacks on web-based applications. Normally these attacks can be prevented by generating a random number or string on the first request to the application. The application saves this token in the session. In subsequent requests the client passes this token along with its requests, and the server validates this token from the client. In this way, malicious websites cannot post valid requests even if they have access to a valid session in a browser.

In Content Services GraphQL, this feature is enabled by default. All applications that use the GraphQL API must pass a ECM-CS-XSRF-Token token in the header and cookie.

A Content Services GraphQL API application is a stateless application, however, and does not maintain any session for the customer. For this reason, different security against attack is required. The GraphQL application looks for an identical token, ECM-CS-XSRF-Token, in every request header as well as in a cookie. The application validates the requests to make sure that these token values are identical. Content Services GraphQL rejects the requests if the token from the client request does not match the expected value. Java applications that use a Rest assured library application can pass the header and cookie as follows:
String xsrfToken = java.util.UUID.randomUUID().toString();
    io.restassured.RestAssured.given()
                                                  .relaxedHTTPSValidation()
                                                  .spec(spec)
                                                  .contentType("application/json")
                                                  .auth().preemptive().oauth2(accessToken )
                                                  .header("Accept", Constants.JSON_CONTENT_TYPE).and()
                                                  .header("Content-Type", Constants.JSON_CONTENT_TYPE).and()
                                                  .header("ECM-CS-XSRF-Token", xsrfToken).and()
                                                  .cookie("ECM-CS-XSRF-Token", xsrfToken).and()
                                                  .body(GraphqlQueryjsonObj.toString())
                                                  .post(graphqlEndPointURL);
To disable this feature in a development environment, set the following JVM argument:
-Decm.content.graphql.xsrf.validate.disable=TRUE
Or, enable the GraphIQL with the following JVM argument:
-Dcom.ibm.ecm.content.graphql.enable.graphiql=TRUE

Do not disable the feature in a production environment.

For any applications that uses Content Services GraphQL, it is recommended that the GraphQL client generates the token and set it as a cookie on the client browser, and saves the same value in the user session. When the client request comes, the GraphQL client can validate the ECM-CS-XSRF-Token cookie against the value that was saved in the session. When the request is passed to Content Services GraphQL, these values are in the header ECM-CS-XSRF-Token and cookie EM-CS-XSRF-Token. If the application that uses the Content Services GraphQL API is stateless, then that application can generate a token and pass the same token in both the header ECM-CS-XSRF-Token and cookie ECM-CS-XSRF-Token to the Content Services GraphQL.

Bypassing the XSRF check for the ping page

You can also configure your environment to bypass the XSRF check for the ping page. In an authoring or development environment, this configuration might be helpful for applications that need to do a health check or access the ping page, but have no way of passing additional headers and cookies in the request.

To bypass the XSRF check for the ping page, set the following JVM argument to TRUE:
-Decm.content.graphql.disable.xsrf.validation.for.ping=TRUE

This JVM argument also sets the random string as the cookie value for ECM-CS-XSRF-Token on the client. This can help for clients with restrictions on appending a cookie to the request that is being sent to the GraphQL server. In those cases, set the -Decm.content.graphql.disable.xsrf.validation.for.ping=TRUE JVM argument on the GraphQL server, access the ping page, then read the ECM-CS-XSRF-Token cookie value and pass the cookie value as the ECM-CS-XSRF-Token token header.

Enabling XSRF validation with GraphiQL enabled

You can enable the XSRF validation at the same time when GraphiQL UI is enabled. Though the default is still to disable XSRF validation if GraphiQL is enabled, the JVM argument can be used to explicitly enable XSRF validation when GraphiQL is enabled. The JVM argument must be set to false along with the option to enable GraphiQL.

-Decm.content.graphql.xsrf.validate.disable=FALSE
-Dcom.ibm.ecm.content.graphql.enable.graphiql=TRUE
Enabling the XSRF cookie HttpOnly attribute

As mentioned earlier in the topic, you can use the ping page to establish the ECM-CS-XSRF-Token cookie. To maintain security standards, the cookies set by the server should also include the HttpOnly attribute. For example,

set-cookie: ECM-CS-XSRF-Token=adfafeeb-566f-4827-b4a8-a0a9a2142124; Path=/; Secure; HttpOnly; SameSite=None

Based on the client, when GraphQL requests are sent with the attribute set, it might not be possible to read the cookie value to use as the token value. For example, javascript, which runs inside a browser is prevented from reading these cookie values. The HttpOnly attribute is not included by default when the ping page sets the ECM-CS-XSRF-Token cookie. It can be enabled with the following JVM argument:

-Decm.content.graphql.xsrf.cookie.httponly.enable=true

A client can still access this token value because the same value is returned by the ping page as a header value. The client can read the header value rather than the cookie value and pass that value back as the ECM-CS-XSRF-Token token header in a subsequent GraphQL request. The following javascript example calls the ping page, and makes a GraphQL request by using the returned token value.

var xsrftok = null;
    function graphQLFetcher(graphQLBody) {
        var pingPath = gqlHost + "/ping";
        var pingHdrs = {
            Accept: 'application/json',
            'Content-Type': 'application/json',
        };
        var gqlPath = gqlHost + "/graphql";
        var hdrs = {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        }
        return fetch(
            pingPath,
            {
                method: 'get',
                headers: pingHdrs,
                credentials: 'include',
            }
        ).then(function(pingResp) {
            var pingXsrf = pingResp.headers.get('ecm-cs-xsrf-token');
            if (pingXsrf) {
                xsrftok = pingXsrf;
            }
            if (xsrftok) {
                hdrs["ECM-CS-XSRF-Token"] = xsrftok;
            }
            return fetch(
                gqlPath,
                {
                    method: 'post',
                    headers: hdrs,
                    body: JSON.stringify(graphQLBody),
                    credentials: 'include'
                },
                ).then(function (response) {
                    return response.json().catch(function() {
                        return response.text();
                    });
                });
            });
    }
 

This example always calls to the ping page before it calls the GraphQL endpoint. In a real application, you need to call the ping page for the first time to establish the XSRF token and then use it in repeated calls to GraphQL.