Skip to Content
Author's profile photo Jerry Wang

Regarding cookie manipulation in CL_HTTP_CLIENT to avoid CSRF token validation failure issue

Recently I meet with this cookie issue so I document it as a blog in case any other guys might meet with the same problem.

I am trying to create some Opportunity transaction data by consuming OData service via CL_HTTP_CLIENT. Since this is a update operation which needs to be finished by HTTP POST, so a CSRF token is needed in this HTTP post. Let’s first have a look what is a typical scenario running in Chrome extension postman:

1. Perform a HTTP get operation with header field x-csrf-token = fetch to get a valid CSRF token from http response header field.
2. launch a HTTP post request using the CSRF token got from previous step,
And a new opportunity could successfully be created in postman:
However, when I implement the above mentioned scenario in ABAP, I meet with trouble. Instead of the expected successful creation message, I always get the error message “CSRF token validation failed”.

Issue trouble shooting

Let’s go back to postman scenario. Although it seems only the header field x-csrf-token is specified in http request, however there is another field cookie which is added to http request under the hood.
We could get this cookie field from previous HTTP get request which is responsible for CSRF token retrieve:
It means in ABAP implementation, we need to first retrieve BOTH CSRF token and cookie field from the first HTTP GET request, and then added these two fields to the request of second HTTP POST request which actually performs the opportunity creation.

This solution is also explained in this thread

I just added the complete source code of my implementation here and feel free to reuse it.
The ABAP report to create Opportunity by consuming OData service:
zcl_odata_tool=>get_csrf_token_and_cookie( IMPORTING et_cookies = DATA(lt_cookie)
                                                     ev_token = DATA(lv_token)  ).
zcl_odata_tool=>create_opp( iv_token = lv_token it_cookies = lt_cookie ).
Source code for ZCL_ODATA_TOOL:
CLASS zcl_odata_tool DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .
  PUBLIC SECTION.
    CLASS-METHODS get_csrf_token_and_cookie
      EXPORTING
        !et_cookies TYPE tihttpcki
        !ev_token TYPE string .
    CLASS-METHODS create_opp
      IMPORTING
        !iv_token TYPE string
        !it_cookies TYPE tihttpcki .
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_ODATA_TOOL IMPLEMENTATION.
  METHOD create_opp.
    DEFINE insert_line.
      lv_body = lv_body && &1.
      lv_body = lv_body && cl_abap_char_utilities=>newline.
    END-OF-DEFINITION.

    DATA:lo_http_client TYPE REF TO if_http_client,
             lv_status      TYPE i,
             lt_fields      TYPE tihttpnvp,
             lv_sysubrc     TYPE sysubrc.

    CALL METHOD cl_http_client=>create_by_url
      EXPORTING
        url                = 'https://<your C4C host>/sap/c4c/odata/v1/c4codata/$batch'
      IMPORTING
        client             = lo_http_client
      EXCEPTIONS
        argument_not_found = 1
        plugin_not_active  = 2
        internal_error     = 3
        OTHERS             = 4.

    ASSERT sy-subrc = 0.
    lo_http_client->propertytype_accept_cookie = if_http_client=>co_enabled.

    CALL METHOD lo_http_client->request->set_method( if_http_request=>co_request_method_post ).

    lo_http_client->request->set_header_field( name = 'Content-Type' value = 'multipart/mixed; boundary=batch_1' ).
    lo_http_client->request->set_header_field( name = 'x-csrf-token' value = iv_token ).
    lo_http_client->request->set_header_field( name = 'Authorization' value = 'your basic authentication code' ).
    LOOP AT it_cookies ASSIGNING FIELD-SYMBOL(<cookie>).
      lo_http_client->request->set_cookie( name = <cookie>-name
                                           value = <cookie>-value ).
    ENDLOOP.
    DATA: lv_body TYPE string.

    insert_line '--batch_1'.
    insert_line 'Content-Type: multipart/mixed; boundary=changeset_1'.
    lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
*
    insert_line '--changeset_1'.
    insert_line 'Content-Type: application/http'.
    insert_line 'Content-Transfer-Encoding: binary'.
    lv_body = lv_body && cl_abap_char_utilities=>cr_lf.

    insert_line 'POST OpportunityCollection HTTP/1.1'.
    insert_line 'Content-Length: 5000'.
    insert_line 'Accept: application/json'.
    insert_line 'Content-Type: application/json'.

    lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
    insert_line '{'.
    insert_line '"AccountID": "8000018122",'.
    insert_line '"OwnerID": "8000018122",'.
    insert_line `"Name": {"content": "Testing ticket creation via OData Jerry1"}`.
    insert_line '}'.
    insert_line '--changeset_1--'.
    lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
    insert_line '--batch_1--'.

    lo_http_client->request->set_cdata( data = lv_body ).
    CALL METHOD lo_http_client->send
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3.

    ASSERT sy-subrc = 0.

    CALL METHOD lo_http_client->receive
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3.

    IF sy-subrc <> 0.
      CALL METHOD lo_http_client->get_last_error
        IMPORTING
          code    = lv_sysubrc
          message = DATA(ev_message).
      WRITE: / 'error occurred during receive data' COLOR COL_NEGATIVE.
      RETURN.
    ENDIF.
    DATA(lv_json) = lo_http_client->response->get_cdata( ).
    WRITE:/ lv_json.
  ENDMETHOD.

  METHOD get_csrf_token_and_cookie.
    DATA:  lo_http_client TYPE REF TO if_http_client,
           lv_status      TYPE i,
           lt_fields      TYPE tihttpnvp,
           lv_sysubrc     TYPE sysubrc.

    CALL METHOD cl_http_client=>create_by_url
      EXPORTING
        url                = 'https://<your C4C host>/sap/c4c/odata/v1/c4codata/'
      IMPORTING
        client             = lo_http_client
      EXCEPTIONS
        argument_not_found = 1
        plugin_not_active  = 2
        internal_error     = 3
        OTHERS             = 4.

    ASSERT sy-subrc = 0.
    lo_http_client->propertytype_accept_cookie = if_http_client=>co_enabled.

    CALL METHOD lo_http_client->request->set_method( if_http_request=>co_request_method_get ).
    lo_http_client->request->set_header_field( name = 'x-csrf-token' value = 'Fetch' ).
    lo_http_client->request->set_header_field( name = 'Accept' value = 'application/json' ).
    lo_http_client->request->set_header_field( name = 'Content-Type' value = 'application/json' ).
    lo_http_client->request->set_header_field( name = 'Authorization' value = 'Your basic authentication' ).

    CALL METHOD lo_http_client->send
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3.

    ASSERT sy-subrc = 0.

    CALL METHOD lo_http_client->receive
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3.

    IF sy-subrc <> 0.
      CALL METHOD lo_http_client->get_last_error
        IMPORTING
          code    = lv_sysubrc
          message = DATA(ev_message).
      WRITE: / 'Error when getting token:', ev_message.
      RETURN.
    ENDIF.

    lo_http_client->response->get_header_fields( CHANGING fields = lt_fields ).

    READ TABLE lt_fields ASSIGNING FIELD-SYMBOL(<field>) WITH KEY name = 'x-csrf-token'.
    ev_token = <field>-value.

    lo_http_client->response->get_cookies( CHANGING cookies = et_cookies ).
    lo_http_client->close( ).

  ENDMETHOD.
ENDCLASS.

Further reading

You can find a list of all other blogs related to OData written by Jerry.

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Manivel Samidurai
      Manivel Samidurai

      Hello Jerry,

      This blog is really helpful for beginners like me. Thanks a lot for writing this.

      I am facing the same error as you have mentioned in the blog. I have done all the steps as suggested in the blog. Yet the issue still persists. I've posted my code below for your perusal. If you could kindly spare few minutes to go through and give any possible suggestions it would be really helpful.

       

      CALL METHOD cl_http_client=>create_by_destination
      EXPORTING
      destination
      = lc_dest
      IMPORTING
      client = lo_http_client
      EXCEPTIONS
      destination_not_found
      = 1
      internal_error
      = 2
      argument_not_found
      = 3
      destination_no_authority
      = 4
      plugin_not_active
      = 5
      OTHERS = 5.

      IF sy-subrc <> 0.
      EXIT.
      ELSE.

      lv_uri = 'My URI'.

      CALL METHOD cl_http_utility=>set_request_uri
      EXPORTING
      request
      = lo_http_client->request
      uri
      = lv_uri.

      * update the HTTP Method
      CALL METHOD lo_http_client->request->set_method
      EXPORTING
      method = lo_http_client->request->co_request_method_get.

      * set Content type
      CALL METHOD lo_http_client->request->if_http_entity~set_content_type
      EXPORTING
      content_type
      = 'application/json'.

      *-set header field for fetching X-CSRF token
      CALL METHOD lo_http_client->request->set_header_field
      EXPORTING
      name
      = 'x-csrf-token'
      value = 'Fetch'.

      * Step 3:- Trigger the GET Method
      lo_http_client
      ->send(
      EXCEPTIONS
      http_communication_failure
      = 1
      http_invalid_state
      = 2 ). "Send the HTTP request

      lo_http_client->receive(
      EXCEPTIONS
      http_communication_failure
      = 1
      http_invalid_state
      = 2
      http_processing_failed
      = 3 ). "receive the response

      *GET Cookie from earlier response

      CALL METHOD lo_http_client->response->get_cookies
      CHANGING
      cookies
      = lt_cookies.

      lo_http_client->propertytype_accept_cookie
      = if_http_client=>co_enabled.

      LOOP AT lt_cookies INTO ls_cookies.

      CALL METHOD lo_http_client->request->set_cookie
      EXPORTING
      name
      = ls_cookies-name
      value = ls_cookies-value.

      ENDLOOP.

      *GET Token from earlier response
      CALL METHOD lo_http_client->response->get_header_field
      EXPORTING
      name
      = 'x-csrf-token'
      RECEIVING
      value = lv_token.

      *-Set X-CSRF- Token in the new request.
      CALL METHOD lo_http_client->request->set_header_field
      EXPORTING
      name
      = 'x-csrf-token'
      value = lv_token.

      * update the HTTP Method

      CALL METHOD lo_http_client->request->set_method
      EXPORTING
      method = lo_http_client->request->co_request_method_post.

      ****content type
      CALL METHOD lo_http_client->request->set_content_type
      EXPORTING
      content_type
      = 'application/json'.

      lv_body = 'My Content'.

      * create Body for the HTTP Post request
      CALL METHOD lo_http_client->request->set_cdata
      EXPORTING
      data = lv_body.

      lo_http_client->send(
      EXCEPTIONS
      http_communication_failure
      = 1
      http_invalid_state
      = 2 ). "Send the HTTP request

      lo_http_client->receive(
      EXCEPTIONS
      http_communication_failure
      = 1
      http_invalid_state
      = 2
      http_processing_failed
      = 3 ). "receive the response

      es_return = lo_http_client->response->get_cdata( ). 

      ENDIF.

      Author's profile photo Naoto Amari
      Naoto Amari

      Did you solved this? i'm facing same issue