API Connect

API Connect

Join this online group to communicate across IBM product users and experts by sharing advice and best practices with peers and staying up to date regarding product enhancements.

 View Only
  • 1.  Preflow Policy to obtain client id from OAuth2 token in Authorization header

    Posted Mon January 08, 2024 09:44 AM
    Edited by Henri Unruh Mon January 08, 2024 09:47 AM

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



  • 2.  RE: Preflow Policy to obtain client id from OAuth2 token in Authorization header

    Posted Tue January 09, 2024 02:43 PM
    Edited by Steve Linn Tue January 09, 2024 04:05 PM

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



  • 3.  RE: Preflow Policy to obtain client id from OAuth2 token in Authorization header

    Posted Wed January 10, 2024 03:19 AM
    Edited by Henri Unruh Wed January 10, 2024 03:21 AM

    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:

    1. Post value of subject field from third-party OAuth provider to the client id of the respective app
    2. Subscribe with this app the respective product
    3. Send API request with third-party OAuth token
    4. Preflow policy extracts subject from token payload, writes subject value to header at API runtime and client identification is performed
    5. 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
    ------------------------------



  • 4.  RE: Preflow Policy to obtain client id from OAuth2 token in Authorization header

    Posted Wed January 10, 2024 02:34 PM
    Edited by Steve Linn Wed January 24, 2024 02:02 PM

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



  • 5.  RE: Preflow Policy to obtain client id from OAuth2 token in Authorization header

    Posted Thu January 11, 2024 10:00 AM
    Edited by Henri Unruh Thu January 11, 2024 10:00 AM

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



  • 6.  RE: Preflow Policy to obtain client id from OAuth2 token in Authorization header

    Posted Fri January 12, 2024 11:31 AM

    Hi Herni,
    Thank you for the update. Yes I would encourage you to open a support ticket for this. I would be surprised that the platform would matter, and a downgrade of your .ova system just for a test to 10.5.0.4 I'd expect would have the same behavior as your k8 cluster.  As for the latter version acting differently, that definitely needs looking into.  I see 10.5.0.9 was released in December and was looking at the APAR details at https://www.ibm.com/support/pages/node/6607067.  I didn't see anything that jumped out at me as to a change that would impact this behavior from  10.5.0.8, but it may be worth a try to upgrade your .ova appliance to see if it is fixed there.
    Best Regards,

    Steve



    ------------------------------
    Steve Linn
    Senior Consulting I/T Specialist
    IBM
    ------------------------------