cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

adding CSRF parameter in service.tx(request).run(request.query)

karthiheyan_murugesan1
Product and Topic Expert
Product and Topic Expert
5,748

Hi ,

Any lead on how to use service.tx(request).run(request.query) for a post call to S/4 odata.

More details

When I use service.tx(request).run(request.query) for a post call, Iam getting CSRF token invalid error in S/4 and thus 403 error in CF.

Iam able to get CSRF token with S/4 URL directly in Tcode /iwfnd/maint_service with header as [X-CSRF-TOKEN : Fetch ].

Iam not able to get CSRF token if i use post using service.tx(request).run(request.query) of SAP CAP Service. [HTTP header is not having the [X-CSRF-TOKEN : Fetch] ]

Exact question

  1. How to set header parameter for getting CSRF token in [ service.tx(request).run(request.query) ] . I want to set [X-CSRF-TOKEN : Fetch] in a GET request header so i can set back CSRF token in post call.

Regards,

Karthi

View Entire Topic
manuel_seeger
Explorer
0 Likes

I had the same problem when accessing the Cloud 4 Customer OData API through an imported external service. Additionally, the C4C API doens't implement HEAD.

I solved it by modifying http-client.js of @sap-cloud-sdk . I added the following intercepter at the end of the file (L195 in version 1.28.1). Note that this is targeting C4C and might need some small changes to make it generic.

function createRequestInterceptor() {
    const interceptor = axios_1.default.interceptors.request.use(async (config) => {
        if (config.url.startsWith('/cust/')) {
            // for customer OData resources, reqrite the baseURL to point to the customer namespace
            config.baseURL = config.baseURL.replace(/\/v1\/c4codataapi/, '')
        }
        if (['head', 'get'].includes(config.method)) {
            config.headers['x-csrf-token'] = 'fetch' 
        }
        
        // intercept post/patch/delete and send a head request
        // to the same resource to fetch the CSRF token
        // the intercepter needs to be ejected before sending the head
        // request to avoid endless recursion
        if (['post','delete','patch'].includes(config.method)) {
            let tokenConfig = __assign({}, config)
            // better would be head, but our target system (C4C) doesn't implement HEAD
            tokenConfig.method = 'get'
            delete tokenConfig.data
            delete tokenConfig.headers['content-type']
            delete tokenConfig.headers['content-length']
            tokenConfig.headers.common['x-csrf-token'] = 'fetch'
            tokenConfig.validateStatus = (status) => {
                return status >= 200 && status < 300 || status == 400; 
            }
            
            try {
                axios_1.default.interceptors.request.eject(interceptor)
                let tokenResponse = await axios_1.default.request(tokenConfig)
                config.headers.common['x-csrf-token'] = tokenResponse.headers['x-csrf-token']
                const cookies = tokenResponse.headers['set-cookie']
                if (cookies) {
                    config.headers.Cookie = cookies.join('; ')
                }
                
            } catch (e){
                if (e.isAxiosError) {
                    console.log(e.response.status, e.response.statusText)
                } else {
                    console.log(e)
                }
                
            } finally {
                createRequestInterceptor()
            }
        }
        return config
    })
}
createRequestInterceptor()

Someone smarter than me can probably come up with a cleaner way to add the interceptor to the http client without having to modify the @sap-cloud-sdk library. Additionally this will request a new CSRF token before every patch/post/delete instead of caching the token.

It's an extremely simple "fix" though and for my purposes it's good enough

gregorw
SAP Mentor
SAP Mentor

Maybe the cdse from jhodel18 helps you.

manuel_seeger
Explorer
0 Likes

Thanks Gregor, I had tried cdse but it didn't work for me and I would have to rewrite it as well to fit C4C. And honestly I find the axios interceptor a bit more elegant than rewriting the destination handling as it's completely agnostic to how axios is configured.

Anyway this works well for my use case and I meant to share this approach as an alternative.