CRM and CX Blogs by Members
Find insights on SAP customer relationship management and customer experience products in blog posts from community members. Post your own perspective today!
cancel
Showing results for 
Search instead for 
Did you mean: 
VishnAndr
Active Contributor
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.
30 Comments
Great solution! Thank you Andrei, I tested and worked for me. It will be saving a lot time for future work.
VishnAndr
Active Contributor
0 Kudos
Nice mate! Thank you! Glad it worked. There is more to come 😉
former_member226
Employee
Employee
It simply works like charm. Thanks Andrei for the share!

 

Regards

Saurabh
kammaje_cis
Active Contributor
Wow! loved this feature.
VishnAndr
Active Contributor
Welcome 🙂
VishnAndr
Active Contributor
Glad you loved it, I use it every day.
kamesh_g
Contributor
Thank you ! it worked perfect .
Hvshal4u
Active Participant
Wonderful (yes) .Thanks
Great content Andrey, Thanks a lot. 🙂
mahesh_1250
Discoverer
Great stuff. Thanks a lot, Andrei. 🙂
Why re-invent the wheal when somebody has already did it in a perfect manner... saved me a lot of time.. Thanks!
york_liu_yong62
Discoverer
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/
VishnAndr
Active Contributor
0 Kudos

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 ?

 

jose_rangel
Participant
0 Kudos
Excelent blog! This is very useful and saves a lot of time.
former_member606813
Participant
0 Kudos
Simple and effective, loved it! Thanks for the code!
05555
Explorer
0 Kudos
Андрей, привет!

Всё прекрасно только данный скрипт не работает для batch запроса. Так должно быть ?
Pavel_Lobach
Participant
0 Kudos
Марина, привет! Я не уверен, на счет batch запросов (обрабатывается ли вообще пакетный ввод стандартным классом CL_REST_HTTP_HANDLER), но проблема может быть в том, что запросы из одного пакета должны, например, выполняться в разных контекстах - тогда их нужно разделять на сессии (разные пары куков и x-csrf-token). Или, наоборот, должны выполняться в одном контексте - в таком случае тебе нужно получить только одну пару куков и -csrf-token и использовать их для всего пакета.
VishnAndr
Active Contributor

Hi 05555 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.

 

NilsO
Advisor
Advisor
0 Kudos

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

05555
Explorer

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.

Pavel_Lobach
Participant
0 Kudos
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.

VishnAndr
Active Contributor
0 Kudos

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

NilsO
Advisor
Advisor
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');

Why using

'?$top=1'

?

 

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

VishnAndr
Active Contributor
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).
VishnAndr
Active Contributor
0 Kudos

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).

mattisebastian
Participant
0 Kudos
Thanks Andrei!

Works like a charm on a custom API (not using OData)!

One thing I noticed: I had a manually added X-CSRF-Header in my request initially but it was disabled in Postman (greyed out, tickbox not marked). Still this script would not run until I removed the header completely.

Best regards

Matti
ondrej_lubovsky
Discoverer
0 Kudos
Super ! .Thank you a lot Andrey
0 Kudos
Thank YOU!
AlanChen
Product and Topic Expert
Product and Topic Expert
0 Kudos
I love this. Thank you!