I am trying to expose a Proxy API that requires mandatory parameters, and we want to isolate API consumers from that sort of implementation details when consuming our API.
The current API does not require an authentication method, it just requires passing the login/password as parameter in the body request + other mandatory parameters.
This is how we currently call the API (without Apigee) including the mandatory params:
To interact with the API in Apigee, consumers are expected to follow this pattern:
https://example.com/lawmas/v1/general-access-contracts
Where:
BasePath = /lawmas/v1
Target Path =/general-access-contracts
We require adding the mandatory parameters in target path, this is what I have so far:
1. I created two Key Values Maps for Encrypted Credentials (login, password) and Mandatory Parameters (KB,table, lan):
2. I added them in the Proxy Endpoint:
Proxy Endpoint > POST /general-access-contracts:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
<Description>LAWMAS - General Access Contracts</Description>
<DefaultFaultRule name="default-fault">
<Step>
<Name>FC-ExceptionHandling</Name>
</Step>
</DefaultFaultRule>
<PreFlow name="PreFlow">
<Request/>
<Response/>
</PreFlow>
<Flows>
<Flow name="OptionsPreFlight">
<Request/>
<Response>
<Step>
<Name>AM-Add_CORS</Name>
</Step>
</Response>
<Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
</Flow>
<Flow name="/general-access-contracts">
<Condition>(proxy.pathsuffix MatchesPath "/general-access-contracts") and (request.verb = "POST")</Condition>
<Description>List `General Access Contract` objects.</Description>
<Request>
<Step>
<Name>KVM-Get_Backend_Credentials</Name>
</Step>
<Step>
<Name>KVM-Get_TargetPathSufix</Name>
</Step>
<Step>
<Name>AM-Set_TargetPath_GeneralAccessContracts</Name>
</Step>
</Request>
<Response/>
</Flow>
<Flow name="UnmatchedPaths">
<Description/>
<Request>
<Step>
<Name>AM-404_Not_Found</Name>
</Step>
<Step>
<Name>RF-Custom_Errors</Name>
</Step>
</Request>
<Response/>
<Condition>(proxy.pathsuffix MatchesPath "**")</Condition>
</Flow>
</Flows>
<PostFlow name="PostFlow">
<Request/>
<Response/>
</PostFlow>
<HTTPProxyConnection>
<BasePath>/lawmas/v1</BasePath>
<VirtualHost>https-external-apis</VirtualHost>
</HTTPProxyConnection>
<RouteRule name="NoRoute">
<Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
</RouteRule>
<RouteRule name="default">
<TargetEndpoint>default</TargetEndpoint>
</RouteRule>
</ProxyEndpoint>
However, it looks like the backend server is not accepting the parameters:
I would highly value any insights or information you could provide
Solved! Go to Solution.
I think maybe you are using Apigee to "mediate security" on this API. In other words, you want the Apigee API Proxy to check one set of credentials (maybe a token) and the upstream (backend?) system should accept a different kind of credential. Maybe it uses 2-way TLS, or something else.
If this i the case, then you may need to strip the credentials that you pass into Apigee, from the request that gets propagated to the target.
From the error message "Wrong Authorization data", it. looks like the upstream is complaining about the authorization data, which is normally passed in an Authorization header. From the curl command you showed, which looks like it's the one that the Apigee Trace system will display for you, it appears there IS an Authorization header being propagated to the upstream system. And also it looks like the OTHER request from Postman, the one that does not go through Apigee, passes no Authorization header at all. And it succeeds.
So what I suspect is that a valid request to the upstream must not include the access token which is known only by Apigee.
To make this work, in the Apigee proxy you might call OAuthV2/VerifyAccessToken, and then immediately after that, use AssignMessage to remove the Authorization header. Configure the step like this:
<AssignMessage name='AM-Remove-Authz-Header'>
<Remove>
<Headers>
<Header name='Authorization'/>
</Headers>
</Remove>
</AssignMessage>
But you may wish to have your approach reviewed by a security architect on your side. Passing the login and password on every request to the upstream is an anti-pattern. Also, from the error message, the EWS system will accept and validate credentials passed in an Authorization header. What I advised above is:
Instead of that, a better approach might be :
There are two kinds of token: one known by Apigee, and one known by the EWS upstream. If you pass an Apigee token to the EWS upstream it will say "Wrong Authorization data". But if you pass an EWS token to the EWS upstream, then it will probably work.
The trick is to figure out how to configure Apigee to get a token from the EWS upstream. Probably you'd do this with a ServiceCallout step, that calls https://ewsupstream.com/token or similar, passing the login/password. Do this as a step during the Apigee token generation (right before OAuthV2/GenerateAccessToken). Then attach the token returned by the EWS upstream, as a custom attribute on the apigee-generated token.
You are setting the variable targetPath,
but I don't see how that is passed to the target. Are you thinking that by setting targetPath, that will implicitly set the path Apigee uses on the target? Not so.
In Apigee, It is possible to set the target URL dynamically. To do that, you can set the target.url
variable, but you must do this in the target request flow. I see your policies are attached to the proxy request flow. Setting target.url
there, won't work, because target.url
gets reset upon initiation of the target request flow.
But, setting target.url
makes most sense when the full url including the hostname, varies across different calls to the target. That's not the case for you, I think. So you shouldn't use it.
In your case, you just want to set query params into the request
. (Your message said that you want to set the PATH, but in HTTP-speak , the path is everything to the left of the ? . queryparams follow the path. It seems like you want to set queryparams). You can do this with (surprise!) the very talented AssignMessage policy, which allows you to set path, verb, headers, query params, and form params, of the request. Using AssignMessage, you can alter and specify what request the target receives, and it can be completely different from what the apigee proxy receives. This is a really powerful and handy capability of the Apigee gateway. But again, you should set the query params of the request message in the target request flow. Preflow, postflow, or condiitional flow, any of those is fine, but the step should be attached to the TargetEndpoint, not the ProxyEndpoint.
To set the queryparams in an AssignMessage step, you can do much like you would in postman:
<AssignMessage name='AM-Set-Qparams'>
<AssignVariable>
<Name>target.copy.pathsuffix</Name>
<Value>false</Value>
</AssignVariable>
<Set>
<QueryParams>
<QueryParam name='$login'>{private.service.login}</QueryParam>
<QueryParam name='$password'>{private.service.password}</QueryParam>
<QueryParam name='$KB'>{KB}</QueryParam>
<QueryParam name='$table'>{table}</QueryParam>
<QueryParam name='$lang'>{lang}</QueryParam>
</QueryParams>
<Path>/ewws/EWSearch/.json</Path>
<Set>
</AssignMessage>
The AssignMessage step is documented here.
The reason there is an AssignVariable for the target.copy.pathsuffix
: in Apigee by default the pathsuffix for the request, in your case something like /general-access-contracts , gets appended to the target URL by default. It seems like you don't want that. In your case, the target request path should always be /ewws/EWSearch/.json , from what I understand. Setting that variable to false means the Apigee pathsuffix won't get appended there.
By the way, Notice there is no AssignTo element in the suggestion I offered. When there is no AssignTo element, AssignMessage will by default assign to message
, which maps to request
when the step is attached to a request flow. This is also documented - check the docs for the AssignTo element. It doesn't hurt to place something like this in your AssignMessage policy:
<AssignTo createNew='false' transport='http' type='request'/>
...but it is ineffectual. It does nothing. I think the reason that element appears so often in AssignMessage policies is that the UI suggests it. But it doesn't do anything, and arguably it's a bug that the UI suggests that thing. To make AssignTo meaningful, you need to specify something in the TEXT of the element, like this:
<AssignTo createNew='false' transport='http' type='request'>myMessage</AssignTo>
But it's not necessary if you are assigning to the request
variable. If you want to be explicit about what you are assigning to, you can do this:
<AssignTo>request</AssignTo>
(All the attributes, createNew and transport and type, are unnecessary, when assigning to the existing request
variable).
One last thing, I saw that you have a configuration like this in one of your policies:
<AssignMessage name="AM-set-TargetPath_GenerateAccessContracts">
<AssignVariable>
<Name>targetPath</Name>
<Value>/ewws/EWSearch/.json?{something_here}{something_else_here}</Value>
</AssignVariable>
...
</AssignMessage>
With the curly braces, it looks to me that you are intending for Apigee to resolve the someting_here and something_else_here as variables, and place what those variables have, into the targetPath. But that won't work, inside a Value element. You need to use the Template element, to use a message template there. It would look like this:
<AssignMessage name="AM-set-TargetPath_GenerateAccessContracts">
<AssignVariable>
<Name>targetPath</Name>
<Template>/ewws/EWSearch/.json?{something_here}{something_else_here}</Template>
</AssignVariable>
...
</AssignMessage>
But I don't think you need that policy, if you do what I suggested above. I'm pointing it out, only because it looks like you have a misunderstanding of how message templates can be used within AssignVariable.
Thank you @dchiesa1! The QueryParam did the trick, when I call the API, the trace shows all the details accurately: target host, target path and the parameters. In fact, if I copy the same API call into Postman, it perfectly works.
Nevertheless, If I use the Apigee API in Postman then I get the same error, I suspect the problem lies in the Authorization config because in our company we use OAuth 2.0 to connect to Apigee, but the original API on the backend server doesn't expect authorization data.
...I attempted passing just the token in Postman (Authorization=Bearer Token), but I'm still encountering the same error. Do you happen to know of any workarounds?
I think maybe you are using Apigee to "mediate security" on this API. In other words, you want the Apigee API Proxy to check one set of credentials (maybe a token) and the upstream (backend?) system should accept a different kind of credential. Maybe it uses 2-way TLS, or something else.
If this i the case, then you may need to strip the credentials that you pass into Apigee, from the request that gets propagated to the target.
From the error message "Wrong Authorization data", it. looks like the upstream is complaining about the authorization data, which is normally passed in an Authorization header. From the curl command you showed, which looks like it's the one that the Apigee Trace system will display for you, it appears there IS an Authorization header being propagated to the upstream system. And also it looks like the OTHER request from Postman, the one that does not go through Apigee, passes no Authorization header at all. And it succeeds.
So what I suspect is that a valid request to the upstream must not include the access token which is known only by Apigee.
To make this work, in the Apigee proxy you might call OAuthV2/VerifyAccessToken, and then immediately after that, use AssignMessage to remove the Authorization header. Configure the step like this:
<AssignMessage name='AM-Remove-Authz-Header'>
<Remove>
<Headers>
<Header name='Authorization'/>
</Headers>
</Remove>
</AssignMessage>
But you may wish to have your approach reviewed by a security architect on your side. Passing the login and password on every request to the upstream is an anti-pattern. Also, from the error message, the EWS system will accept and validate credentials passed in an Authorization header. What I advised above is:
Instead of that, a better approach might be :
There are two kinds of token: one known by Apigee, and one known by the EWS upstream. If you pass an Apigee token to the EWS upstream it will say "Wrong Authorization data". But if you pass an EWS token to the EWS upstream, then it will probably work.
The trick is to figure out how to configure Apigee to get a token from the EWS upstream. Probably you'd do this with a ServiceCallout step, that calls https://ewsupstream.com/token or similar, passing the login/password. Do this as a step during the Apigee token generation (right before OAuthV2/GenerateAccessToken). Then attach the token returned by the EWS upstream, as a custom attribute on the apigee-generated token.
Thank you! @dchiesa1 it worked, I just added the policy to remove the auth header in Target Endpoint PreFlow.