Handling x-csrf-token with SAP PI/PO
Some Web applications are securing their applications with the x-csrf-token. This requires you to call the service to get a token before you do the modification of the objects. This is at the moment not support the REST adapter in SAP PI/PO. But it could probably be added later.
The scenario looks like the following. In our message mapping, we will perform a lookup call to a Rest adapter to get the x-csrf-token. Then we have an adapter module that we use to call the service with.
You can see the video where I’m describing how to setup the system.
Here is the UDF method that calls to get the token.
@LibraryMethod(title="lookupToken", description="get token from service", category="RestLookup", type=ExecutionType.SINGLE_VALUE)
public String lookupToken (
Container container) throws StreamTransformationException{
AbstractTrace trace = container.getTrace();
Map<String, Object> all = container.getInputHeader().getAll();
Channel restChannel = LookupService.getChannel("B2B","ReceiverRestToken");
SystemAccessor restAccessor = LookupService.getSystemAccessor(restChannel);
trace.addInfo("got channel ");
XmlPayload payload = LookupService.getXmlPayload( new ByteArrayInputStream("<root/>".getBytes()));
Payload response = restAccessor.call(payload);
trace.addInfo("got response");
String payloadContent = "";
try {
payloadContent = readbytes(response.getContent());
trace.addInfo(payloadContent);
} catch (IOException e1) {
trace.addWarning("IO Exception " +e1.getMessage());
}
try{
DynamicConfiguration config = (DynamicConfiguration)all.get(StreamTransformationConstants.DYNAMIC_CONFIGURATION);
//Define key to write in the Dynamic Configuration
DynamicConfigurationKey key1 = DynamicConfigurationKey.create("http://sap.com/xi/XI/System/REST","token");
config.put(key1,payloadContent );
}catch(Exception e){
trace.addWarning("Unable to set dynamic configuration" );
}
return "";
}
private String readbytes(InputStream inputStream) throws IOException{
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
// StandardCharsets.UTF_8.name() > JDK 7
return result.toString("UTF-8");
}
Then we have the adapter module that you need to include a software component and deploy together with your other modules.
public class SetTokenModule implements Module, SessionBean {
/**
*
*/
private static final long serialVersionUID = -4916672969815060014L;
private static final Location LOC = Location.getLocation(SetTokenModule.class);
private AuditAccess audit;
@Override
public ModuleData process(ModuleContext context, ModuleData inputmoduleData) throws ModuleException {
try {
Message msg = (Message) inputmoduleData.getPrincipalData();
MessageKey key = msg.getMessageKey();
audit = PublicAPIAccessFactory.getPublicAPIAccess()
.getAuditAccess();
audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS,
"TokenModule: Module called");
MessagePropertyKey tokenKey = new MessagePropertyKey("x-csrf-token","http://sap.com/xi/XI/System/REST");
String token = msg.getMessageProperty(tokenKey);
audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS,
"TokenModule: Tokenvalue "+token);
TextPayload tokenPayload = msg.createTextPayload();
tokenPayload.setText(token);
msg.setMainPayload(tokenPayload);
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return inputmoduleData;
}
}
The post channel configuration should look like the following.
And to get the token to the adapter you need to add the header properties like the following.
Hi Daniel,
Nice, I currently do the same (manually) from Postman while testing S4 HANA Cloud API directly, without using CPI. Some of them, especially POST (Create CRUD opeations) require x-csrf-token for communication, not only basic authentication. It's nice that you share the code Daniel. Thanks!
BR,
Piotr
Hi Daniel,
great work. I was looking for this very long. But you also get the token, while the message mapping, right? There the message mapping is bind to the receiver channel. What if the message goes to many channels and all uses different tokens? Only a thought? But so I came up with the idea to put it all in the adapter module but I had much problems with implementing it. Do you think this is possible, to put it all in a adapter module and so every channel can use its own token an the message mapping is independent from the channel?
Best Regards,
Thomas
Hi Thomas,
Yes I guess you will need to place it in adapter module. I'm not sure if the adapter lookup is available there but you should be able to use some other ways to lookup channels and invoke them form a module context.
The cool thing would be if you could get the rest adapter to request the token self, instead of having to code different around it.
An alternative is that you perform the lookup in the message mapping and then save multiple tokens in the dynamic context and then in your channel, you just select the correct token that corresponds to the messag.e
Hi Daniel,
I'm unable to use UDF as I get the read bytes error. We are using SAP PO tool to build the udf. Is there anything specific to that?
Hi
The UDF above is edited in Eclipse hense the annotation.
Thanks, Daniel
I have one doubt, I’m able to extract token and session id but how to set both the values using SetMainPayload method. I have to pass both the session id and the token in the header.
Is this correct?
String token = msg.getMessageProperty(tokenKey);
audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS, "TokenModule: Tokenvalue " + token);
String cookie = msg.getMessageProperty(setCookie);
audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS, "TokenModule: SetCookie " + cookie);
// Create text payload with Token and Cookie. This will be used by the UDF to
// set token and cookie as dynamic configuration parameters.
TextPayload tokenPayload = msg.createTextPayload();
TextPayload cookiePayload = msg.createTextPayload();
tokenPayload.setText(token);
cookiePayload.setText(cookie);
msg.setMainPayload(tokenPayload);
msg.setMainPayload(cookiePayload);
Hi
In the part where you send it to get the token and want to add a cookie, then you can create a json string like {token: "tokenvalue", cookie: "cookie"} and then in your adapter use the rest adapter and fetch the two payloads out using the mapping.
You should add the variables as dynamic attributes as i do in the last part of the mapping.
Thanks Daniel,
I got this in a different way.
Now Rest Adapter has inbuilt feature to handle XSRF authentication.
2757524 - New Feature: XSRF token support in REST adapter
Hi Manoj
Thanks. I can see it is a part of 7.5 SP 15 that seems to be released now.
Hello Manoj,
have you any practical experience with this feature?
Hello
Please suggest. Many Thanks!
https://answers.sap.com/questions/12882619/how-to-store-and-retrieve-oauth-tokens-in-sap-po.html
I have a similar problem, in a first GET request the token is requested, a second POST request passes the token with user and password and the receiving system answers me with csrftoken, which are stored as cookies to finally make the call to the web-services, hoping I was clear, have you ever found an authentication like this?
Thanks
Umberto