Hi All
I have a requirement to cache oauth token responses. I am managing the oauth token call using urlopen within Gatewayscript. After playing around for a bit, I was able to get caching working by defining a fixed dynamic caching policy, however, whenever I add an Authorization header to the request, Datapower will not honour the fixed cache policy.
I am curious if there is a way around this, or if there are alternative and better solutions to caching the token. Unfortunately, the backend oauth provider will not support client_secret based authentication at this stage. In our current implementation we have achieved caching by routing through an intermediate sub-service. So we cache the sub-service (without Authorization) rather than the actual backend /token call.
Below is a snippet of the code I was playing around with. As mentioned, the caching works until I add the Authorization header, so I think it is by design that the cache is not honoured. Note this is intended to be an APIC service, so I found that using a dynamic policy was the only option within Gatewayscript. I believe this is how the Invoke function operates in the background too.
One alternative solution I was considering was using the distributed-metadata module and managing the cache manually myself. I am not sure if this is an antipattern or not, so seeking advice from the community.
Options considered (using APIC):
- Cache using Invoke (doesn't work with Auth header)
- Cache in gws using dynamic cache policy (doesn't work with Auth header)
- Defining custom gateway extension (would rather avoid, and I imagine same issue as above)
- Cache by using intermediate service that maps Auth header.
My question is
- What is the most appropriate way to manage caching in APIC using Gatewayscript?
- Is there any way to force the cache even when there is an Authorization header present.
Thanks.
20250613T042616.160Z [0x80e008c5][network][debug] apigw(): tid(15595569)[10.245.104.199] gtid(196c5565684ba86800e2aa23): Document cache: cannot cache due to the request has the 'Authorization' header: URL http://example.com:1236/sap/bc/sec/oauth2/token
const sm = require('service-metadata')
const urlopen = require('urlopen');
const { URLSearchParams } = require('url');
const ttl = 900 // 15 minutes
const host = 'https://example.com'
const path = '/oauth2/token'
const endpoint = `${host}${path}`
const clientId = 'client'
const clientSecret = 'secret'
const authorization = Buffer.from(clientId + ":" + clientSecret).toString('base64');
const cachingPolicy = `
<dcp:caching-policies xmlns:dcp="http://www.datapower.com/schemas/caching">
<dcp:caching-policy url-match="${host}${path}" priority="200">
<dcp:fixed ttl="${ttl}"
cache-post-put-response="true"
cache-backend-response="true"
http-cache-validation="false"
return-expired-document="false"
restful-invalidation="false"
/>
</dcp:caching-policy>
</dcp:caching-policies>`
sm.setVar('var://service/cache/dynamic-policies', cachingPolicy)
const data = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
client_id: clientId,
client_secret: clientSecret,
assertion: 'blah',
scope: 'scope'
}).toString()
try {
urlopen.open({
target: endpoint,
method: 'post',
data,
headers: {
'Accept': 'application/json',
//'Authorization': `Basic ${authorization}`,
'x-dp-cache-key': 'test' // ie. always return cache.
},
contentType: 'application/x-www-form-urlencoded',
timeout: 60
}, (error, response) => {
if (error) {
return context.message.body.write({
message: 'urlopen error',
error,
context
});
}
response.readAsBuffer((error, body) => {
if (error) {
return context.message.body.write({
message: 'read token error',
error,
context
});
}
context.message.body.write(body);
})
})
} finally {
sm.setVar('var://service/cache/dynamic-policies',
`<dcp:caching-policies xmlns:dcp="http://www.datapower.com/schemas/caching"/>`)
}
------------------------------
Brendon Stephens
------------------------------