Original Message:
Sent: Thu January 11, 2024 09:59 AM
From: Henri Unruh
Subject: Preflow Policy to obtain client id from OAuth2 token in Authorization header
Hi Steve,
thanks again for your reply. We managed to get it to work, but with the original code I posted in the thread. It was working all the time, just not on the DataPower gateway version we were testing it on.
I was testing on an OVA DataPower gateway VM running version 10.5.0.8. On this version and form factor the client identification still fails. We have another gateway on a native Kubernetes cluster on AWS running version 10.5.0.4. Here the client identification for the exact same API worked fine.
The API is of course deployed on the same manager instance, same catalog/space. The only difference we see, is the deployment form factor and the minor version.
Do you happen to know why that might be? In any case we will create an IBM support case for this.
Thanks again and best regards,
Henri
------------------------------
Henri Unruh
Original Message:
Sent: Wed January 10, 2024 02:33 PM
From: Steve Linn
Subject: Preflow Policy to obtain client id from OAuth2 token in Authorization header
Hi Henri,
Thanks for the additional information! Are you sure your log messages have X-IBM-Client-Id
in this mixed case? Your code has the text for the log message in lower case:
console.debug(`x-ibm-client-id set to ${jsonTokenPayload.sub}`); context.set("request.headers.x-ibm-client-id", jsonTokenPayload.sub); console.debug(`x-ibm-client-id set to ${context.get("message.headers.x-ibm-client-id")}`);
So you have two log messages at the same time that, and assuming your messages say x-ibm-client-id
(lower case) they'd come from these two console.debug statements. The second is logging message.headers.x-ibm.client-id
. Since you properly set the header in the request headers, that wouldn't impact message.headers which is why that log message in the current code is providing the undefined value. Also as noted in my last post, context.get
is case sensitive for the header name (or whatever you're getting out of context), whereas context.request.header.get('x-ibm-client-id')
is header specific where the header name is case insensitive, so at least for the get of a header value, it's a best practice to use that instead of context.get.
As to the client identification failure, if you can enable debug logging in DataPower there are a number of log messages related to the client-identification policy. I find debug logging helpful in finding the policy specific log message. Do any client identification policy logs provide any specific details as to the reason for the failure? For example, I just tried a test where I provided a bogus client-id and I see in the logs the error
Cannot find the subscriber '036c5e3f0d651bcb12c6fd43ed9b5e16' from the API Manager.
a few log records later I see the log that you referenced
request default-preflow-rule #4 api-client-identification: Performed the API client identification action. failed: Cannot pass the client identification check that is required by the target API or operation.
Hopefully the root cause will be just a few log messages prior to the one you cite.
Also, if you can test your transaction from the API Manager Test tool and you have the Probe enabled, in addition to getting an error response in your response body tab, the tracing tab will allow you to follow what is in the API Gateway context prior to every policy, including the preflow rule's policies. In your case you should be able to see in request.headers your Authorization header prior to your preflow policy GatewayScript but no x-ibm-client-id header, and the next policy (probably a built-in policy) that follows would contain the context after your GatewayScript where you should be able to verify that the context's request.headers.x-ibm-client-id header was updated properly. I'm suspecting that it should also contain the XYZ value you indicate is in your token, but that client id must not be known which is why you're failing, but hopefully the logs will bear that out too. Hope this helps!
Best Regards,
Steve
------------------------------
Steve Linn
Senior Consulting I/T Specialist
IBM
Original Message:
Sent: Wed January 10, 2024 03:19 AM
From: Henri Unruh
Subject: Preflow Policy to obtain client id from OAuth2 token in Authorization header
Hi Steve,
thanks a lot for your message.
First of all, regarding the variables in the snippet, I did not include everything here for brevity. These variables were actually initialized, sorry.
Our goal is to perform client identification just from the OAuth token of an third-party OAuth provider. (We only use third-party OAuth providers)
Right now we perform client identification normally by a static client-id header + client-secret header for additional security. We want to get rid of this requirement and use the subject field from the payload of the third-party OAuth token for client identification as described above by writing this in the preflow policy to the x-ibm-client-id header.
By this we would get rid of the static client-id header. The security requirement defined in the API would still be:
'
security: - OAuth: [] clientIdHeader: []
But the consumer would only have to send the OAuth token from which - using our preflow policy - client identification is performed.
So the process would be:
- Post value of subject field from third-party OAuth provider to the client id of the respective app
- Subscribe with this app the respective product
- Send API request with third-party OAuth token
- Preflow policy extracts subject from token payload, writes subject value to header at API runtime and client identification is performed
- Additional steps like API security are performed ...
From gateway logs we see:
Performed the API client identification action. failed: Cannot pass the client identification check that is required by the target API or operation.
We also see:
X-IBM-Client-Id set to XYZ
This is coming from the the console log of the preflow policy. However there is another console log at the same time for this transaction which states:
X-IBM-Client-Id set to undefined
This is probably where the issue lies. How can it be that the variable is logged with the correct value once and in another log as undefined? Timestamps of both logs are exactly the same.
Is there anything we are missing here?
Thanks a lot for your help!
Best regards,
Henri
------------------------------
Henri Unruh
Original Message:
Sent: Tue January 09, 2024 02:43 PM
From: Steve Linn
Subject: Preflow Policy to obtain client id from OAuth2 token in Authorization header
Hi Henri,
I'm not an OAuth SME, but looking at the GatewayScript snippet above, I don't see variables explicitClientIdHeader and authHeader initialized, and perhaps the code is also missing an end } in your cut and paste. The only thing I can add, assuming the token is not encrypted or signed in any way, is that setting the request header should use context.request.header.set('X-IBM-Client-Id', jsonTokenPayload.sub);
instead of context.set
, at least that is what the documentation indicates. However, since you say you're seeing the client-id in the DataPower logs and it is setting the header properly, the fact that you're global preflow policy is before-builtin
should have this executed before any built in preflow policy including client-identification. Are you sure this client id is subscribed to the API? What logs are you seeing indicating the failure? What type of security requirements does your API have? What is your use case to do this? I've been discussing this with some of the OAuth SMEs and they indicate this can't be a token issued by a APIC native OAuth provider. Who issued the token?
Best Regards,
Steve
------------------------------
Steve Linn
Senior Consulting I/T Specialist
IBM
Original Message:
Sent: Mon January 08, 2024 09:43 AM
From: Henri Unruh
Subject: Preflow Policy to obtain client id from OAuth2 token in Authorization header
Hi team,
we want to use a preflow policy to obtain the client id from the subject field in the payload of an incoming OAuth2 token, write the value to a header "x-ibm-client-id" and perform client identification from this header.
However, the API client identification at API runtime fails. We get "401 Unauthorized" with response body: "Invalid client id or secret".
The client id from the token was of course added to the respective app. From the gateway logs, we see that the value of the subject field is extracted and written to the "x-ibm-client-id" header correctly. The policy is deployed as mode: "before builtin".
Here is a snippet from the policy code:
var apim = require("apim"); main(); function main() { // Check if x-ibm-Client-ID is set // Check if Authorization header is NOT set // -> If either is true, skip extraction if (!explicitClientIdHeader && authHeader) { try { console.debug(`x-ibm-client-id not set. Trying to fetch from Authorization Header.`); // -> ClientID not set, Auth Header is present -> try fetching from provided Auth Header var [tokenType, authParsedHeaderToken] = authHeader.split(" "); console.debug(`Token Type: ${tokenType}`); console.debug(`Token: ${authParsedHeaderToken}`); if (tokenType === "Bearer" && authParsedHeaderToken) { // Decode base64 // Split and keep the second part as it is the token body var tokenBodyDecoded = Buffer.from( authParsedHeaderToken.split(".")[1], "base64" ).toString("utf-8"); var jsonTokenPayload = JSON.parse(tokenBodyDecoded); console.debug(`x-ibm-client-id set to ${jsonTokenPayload.sub}`); context.set("request.headers.x-ibm-client-id", jsonTokenPayload.sub); console.debug(`x-ibm-client-id set to ${context.get("message.headers.x-ibm-client-id")}`); } catch (error) { console.info("Token parsing was not possible.", error); } } else { // -> ClientID is set OR Auth Header not provided -> skip token extraction logic } }
Is there anything we are missing here?
Thank you and kind regards
Henri
------------------------------
Henri Unruh
------------------------------