Skip to Content
Technical Articles
Author's profile photo Andrey Vishnevskiy

CSRF token in Postman. One click to get it and use it.

This blog is inspired by an excellent blog “Just a single click to test SAP OData Service which needs CSRF token validation” authored by Jerry Wang

I liked the approach Jerry shared. Each time you need to create, update or delete some data via (SAP) oData API you need to use CSRF token (e.g. it’s applicable to C4C oData API). It used to be quite a pain in Postman. Jerry suggested using an environment variable in Postman to share CSRF token between 2 (or more) requests. Where the first request is getting CSRF token for you and stores it in an environment variable while subsequent requests consume this CSRF token via the variable. Sounds logical.

However, in my case, the need to run a collection (of requests) each time when I need to do a quick and simple POST or PUT or PATCH to C4C oData API was not something I would be comfortable with.

I would prefer “real one-click”. Just hit the Send button in Postman and here we go. Something similar to OData Explorer tool available in C4C system where you don’t need to care about CSRF token at all. Frankly, it’s a great tool, but it has some performance issues when you launching it or navigating from one “heavy” entity type to another. And the error handling is another question which, in my opinion, oData Explorer needs to address to show the complete error message produced by the backend of C4C.

Postman beast is still a preference of mine.
So I wanted to improve Jerry’s approach to make it a “real one-click”.

A bit of research and play with Postman on one of business trips’ flights got me to the idea. And the idea was to use Pre-requests Script in Postman. They are powerful. As powerful as Test scripts. Or even more.

Here is the pre-request script I’ve put together. Console logs are there just for test purposes. Feel free to remove them if you’re clear on what the script is doing and when. You can see those logs in Postman Console if you open it before doing the call to your oData API. Postman Console is available either via menu View -> Show Postman Console or hotkey Alt+Ctrl+C.

console.log('Pre-request Script from Request start');

// We don't need to do anything if it's GET or x-csrf-token header is explicitly presented
if (pm.request.method !== 'GET' && !(pm.request.headers.has('x-csrf-token'))) {

  var csrfRequest = pm.request.clone();
  csrfRequest.method = 'GET';
  if (pm.request.method === 'POST') {
    // for POST method usually it is ....<something>Collection in the URL
    // so we add $top=1 just to quickly get csrf token; 
    // for PUT, PATCH or DELETE the same URL would be enough,
    // because it points to the actual entity
    csrfRequest.url = pm.request.url + '?$top=1';
  }

  csrfRequest.upsertHeader({
    key: 'x-csrf-token',
    value: 'fetch'
  });

  pm.sendRequest(csrfRequest, function(err, res) {
    console.log('pm.sendRequest start');
    if (err) {
      console.log(err);
    } else {
      var csrfToken = res.headers.get('x-csrf-token');
      if (csrfToken) {
        console.log('csrfToken fetched:' + csrfToken);
        pm.request.headers.upsert({
          key: 'x-csrf-token',
          value: csrfToken
        });
      } else {
        console.log('No csrf token fetched');
      }
    }
    console.log('pm.sendRequest end');
  });
}

console.log('Pre-request Script from Request end');​

 

The logic here is:

  1. We’re getting the original request and checking if we need to obtain CSRF token or not (we don’t need CSRF token if we’re doing GET or if the token already presented explicitly).
  2. If we’re unlucky enough and we need to obtain CSRF token, we’re cloning the original request. I didn’t find any other way to get the authentication part from the original request into a new request properly and dynamically.
  3. Having the cloned request, we’re immediately changing its method to GET.
  4. Then we’re enriching the URL of the cloned request for performance reason if we need to.
  5. And populating x-csrf-token header of the cloned request with the value “fetch” barging for a token.
  6. As a next step, we’re sending this cloned and modified request providing a call back function. This function will be executed once the request is completed.
  7. In this call back function, we’re checking for any errors, then looking for x-csrf-token header returned to us and if it’s fetched, we’re upserting it (updating if exists, creating if it doesn’t) into the original request.

To use this script, simply copy the code provided and paste it into the tab called Pre-request Script in your Postman’s request. Then click Send to send your POST/PUT/PATCH/DELETE request to C4C oData API.

You can even go further and put this script either into your Folder or Collection in Postman. And then the script will run for any request you’re doing within those folders or collections. You can find out more on the sequence of scripts in Postman documentation.

Assigned Tags

      26 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Cagri Senol
      Cagri Senol

      Great solution! Thank you Andrei, I tested and worked for me. It will be saving a lot time for future work.

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Nice mate! Thank you! Glad it worked. There is more to come 😉

      Author's profile photo Saurabh Kabra
      Saurabh Kabra

      It simply works like charm. Thanks Andrei for the share!

       

      Regards

      Saurabh

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Welcome 🙂

      Author's profile photo Krishna Kishor Kammaje
      Krishna Kishor Kammaje

      Wow! loved this feature.

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Glad you loved it, I use it every day.

      Author's profile photo Kamesh Gonugunta
      Kamesh Gonugunta

      Thank you ! it worked perfect .

      Author's profile photo Vishal Hingole
      Vishal Hingole

      Wonderful (yes) .Thanks

      Author's profile photo Michel Fernandes
      Michel Fernandes

      Great content Andrey, Thanks a lot. 🙂

      Author's profile photo Mahesh Surabattula
      Mahesh Surabattula

      Great stuff. Thanks a lot, Andrei. 🙂

      Author's profile photo Andrei Nicolae Plaiasu
      Andrei Nicolae Plaiasu

      Why re-invent the wheal when somebody has already did it in a perfect manner... saved me a lot of time.. Thanks!

      Author's profile photo YORK LIU
      YORK LIU

      It works when there is no variable in the URL, However, when there is {{HOST}} variable, it could not resolve the variable in the replaced GET request.

      for example : {{C4C_Host}}/sap/c4c/odata/v1/c4codataapi/CustomerOrderCollection/

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Hi York,

      Glad it worked for you. The original intention of the blog post was to provide the simplest solution possible for “real one-click” approach. Setting up variables and including them in the URL – it’s already not one click but many, don’t you agree? ?

      And Postman… Well, Postman doesn’t help in pre-scripts much unfortunately. The script doesn’t resolve the variables by itself. Unless we do something about it. For example, replace the following line from the original script:

      csrfRequest.url = pm.request.url + '?$top=1';

      with a bit extended version:

      csrfRequest.url = pm.variables.replaceIn(pm.request.url) + '?$top=1';

      and you’re good to go even with variables in the URL ?

       

      Author's profile photo Jose Rangel
      Jose Rangel

      Excelent blog! This is very useful and saves a lot of time.

      Author's profile photo RahulDeep Attri
      RahulDeep Attri

      Simple and effective, loved it! Thanks for the code!

      Author's profile photo Marina Shakalei
      Marina Shakalei

      Андрей, привет!

      Всё прекрасно только данный скрипт не работает для batch запроса. Так должно быть ?

      Author's profile photo Pavel Lobach
      Pavel Lobach

      Марина, привет! Я не уверен, на счет batch запросов (обрабатывается ли вообще пакетный ввод стандартным классом CL_REST_HTTP_HANDLER), но проблема может быть в том, что запросы из одного пакета должны, например, выполняться в разных контекстах - тогда их нужно разделять на сессии (разные пары куков и x-csrf-token). Или, наоборот, должны выполняться в одном контексте - в таком случае тебе нужно получить только одну пару куков и -csrf-token и использовать их для всего пакета.

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Hi Marina Shakalei and Pavel Lobach , long time no see, hoping you guys been well.

      It has been quite some time since I last used Postman. I moved to Insomnia while ago. Feels fresher and lighter to my personal taste. And csrf token handling can be easily achieved there without any script.

      But back to your question. The script works just fine even for $batch requests with C4C OData API. I just checked. Yes, it is making an erroneous call for $batch to fetch a token (for example, to .../sap/c4c/odata/v1/c4codataapi/$batch?$top=1). Yes, it gets 400 status code in response. But still even for a such faulty call, C4C OData API provides a valid CSRF token back. 

      You can check how it goes in Postman Console (menu View -> Show Postman Console) where the script writes all console.log outputs to. You can even see there the GET call to fetch the token. And check there the response/request if any doubts.

       

      Author's profile photo Marina Shakalei
      Marina Shakalei

      Hi Andrei.
      Your knowledge is very valuable. You are right about $batch requests, they work as expected. I was inattentive and didn't notice that in the header I only deactivated the token, not deleted it. After removing it from the header, it works fine. Thanks for taking the time and checking it again.

      Author's profile photo Nils Obermiller
      Nils Obermiller

      Hi Andrei,

      Many thanks for this blog! The script works fine for me if I provide the authorization information (e.g. username / password for basic auth.) at the request itself. But it does not work if I provide the authorization information on the parent (folder or collection). It seems like pm.request.clone(); does not inherit authorization information from the parent. Any idea how to get the authorization information from the parent in the pre-requisite script?

      Best regards,
      Nils

      Author's profile photo Pavel Lobach
      Pavel Lobach

      Hi Nils,

      I'm using collection variables for that with upserting the headers params similar like in the example script of Andrei:

          // header
          csrfRequest.upsertHeader({
              key: 'Authorization',
              value: 'Basic ' + pm.variables.get("gv_basic_auth")
          });
          csrfRequest.upsertHeader({
              key: 'x-csrf-token',
              value: 'fetch'
          });

      However, I believe you can find a way to upsert the auth data from the authorization part of the collection.

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Hi Nils,

      I'd suggest checking the following open Postman issue and its duplicates. Seems like nothing we can do to properly inherit the auth at the moment.

      https://github.com/postmanlabs/postman-app-support/issues/4396

      Author's profile photo Nils Obermiller
      Nils Obermiller

      Hi Andrei,

      Thanks for the link, I'll check the github issue.

      Btw, I adapted your pre-request script a bit to fetch the CSRF token with a HEAD request to the service document URL.Getting the service document URL out of the actual request URL was a bit tricky, but the following works for me with OData V2 and OData V4. The HEAD request does not trigger any data retrieval in Gateway and is a bit faster than GET because Gateway is not required to start up.

      console.log('Pre-request Script from Request start');
      
      // We don't need to do anything if it's GET or x-csrf-token header is explicitly presented
      if (pm.request.method !== 'GET' && !(pm.request.headers.has('x-csrf-token'))) {
      
          var csrfRequest = pm.request.clone();
      
          csrfRequest.method = 'GET';
          csrfRequest.body = '';
      
          // HEAD request to OData service document URL is fasted approach to get CSRF token.
          var urlString = csrfRequest.url.toString().toLowerCase() + '/';
          var v4Index = urlString.indexOf('/sap/opu/odata4/');
      
          // OData V4 Service (URL: https://host:port/sap/opu/odata4/<service group namespace>/<service group>/<respository>/<service namespace>/<service>/<service version>)
          if (v4Index >= 0) { 
              csrfRequest.method = 'HEAD';
              // Calculate OData V4 Service Document URL (w/o any Entity Sets, Query Options etc.)
              csrfRequest.url = urlString.slice(0, v4Index + 16) + urlString.slice(v4Index + 16).split('/', 6).join('/') + '/';
          }
          else {
              var v2Index = urlString.indexOf('/sap/opu/odata/');
              /// OData V2 Service (URL: https://host:port/sap/opu/odata/<service namespace>/<service>)
              if (v2Index >= 0) { // 
                  csrfRequest.method = 'HEAD';
                  // Calculate OData V2 Service Document URL (w/o any Entity Sets, Query Options etc.)
                  csrfRequest.url = urlString.slice(0, v2Index + 15) + urlString.slice(v2Index + 15).split('/', 2).join('/') + '/';
              }
              else if (pm.request.method === 'POST') { // Fallback in case service document URL could not be calculated
                  // for POST method usually it is ....<something>Collection in the URL
                  // so we add $top=1 just to quickly get csrf token; 
                  // for PUT, PATCH or DELETE the same URL would be enough,
                  // because it points to the actual entity
                  csrfRequest.url = pm.request.url + '?$top=1';
      
              }
          }
      
          csrfRequest.upsertHeader({
              key: 'x-csrf-token',
              value: 'fetch'
          });
      
      
          pm.sendRequest(csrfRequest, function (err, res) {
              console.log('pm.sendRequest start: ' + csrfRequest.method + ' ' + csrfRequest.url);
              if (err) {
                  console.log(err);
              } else {
                  console.log('Status: ' + res.code + ' (' + res.status + ')');
                  var csrfToken = res.headers.get('x-csrf-token');
                  if (csrfToken) {
                      console.log('csrfToken fetched:' + csrfToken);
                      pm.request.headers.upsert({
                          key: 'x-csrf-token',
                          value: csrfToken
                      });
                  } else {
                      console.log('No csrf token fetched');
                  }
              }
              console.log('pm.sendRequest end');
          });
      }
      
      console.log('Pre-request Script from Request end');
      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Great, Nils! I like what you did there.

      However, the primary use case for this script (at least when I was crafting it) was to handle SAP C4C OData requests. And SAP C4C OData API doesn't support HEAD method. Not at the time of writing (it doesn't support it still - I just checked).

      Author's profile photo Artyom Vecherov
      Artyom Vecherov

      Why using

      '?$top=1'

      ?

       

      Isn't it easier to use the HEAD http method to avoid receiving body at all?

      Author's profile photo Andrei Vishnevsky
      Andrei Vishnevsky
      Blog Post Author

      Very good question, Artyom.

      The primary use case for this script was to handle SAP C4C OData requests. And SAP C4C OData API doesn't support HEAD method. Not at the time of writing (it doesn't support it still - I just checked).