Service Callout Policy not fetching the Salesforce OAuth Token

Not applicable

(I'm integrating Apigee with Salesforce) - pretty much implementing this scenario from Apigee docs, https://docs.apigee.com/api-services/cookbook/using-policy-composition

I'm trying to hit the proxy https://{host}/{basepath}/sfdc-dealer-codes (this is the API I created in Apigee) which internally has a service call out to external proxy, provided by Salesforce, which gives the token back to apigee and then hits the actual target with the salesforce OAuth token.

The problem here is: when I hit the OAuth Salesforces API directly from Postman, I get the OAuth token, everything works - great! BUT when I use the service callout policy to call the external API provided by salesforce to get the salesforce oauth token I get an error unsupported grant_type=password error.

Please let me know I can provide more information on this. Also, I new to Apigee, any inputs would be much appreciated.

Please refer to this trace from the UI servicecallouterror.jpg

Thanks in anticipation

1 2 1,369
2 REPLIES 2

It's hard to know what the problem is. Can you show the ServiceCallout policy? Can you show the call from postman?

This call works for me from curl:

$ curl -i https://login.salesforce.com/services/oauth2/token \
   -d "grant_type=password" -d "client_id=${CLIENTID}" \
   -d "client_secret=${CLIENTSECRET}" \
   -d "username=${SFDCUSER}" -d "password=${SFDCPWD}" 

One thing you may want to check - do you have IP address restrictions for the connected app? If you are using a ServiceCallout, the ipaddress will be different and SFDC may reject the request-for-token based on that. I had to change some things in the SFDC panels to allow this. To do so, go into the panels in SFDC and

  1. Select Connected Apps
  2. select your app
  3. click Edit Policies
  4. under OAuth policies
    1. Permitted Users: all users may self-authorize
    2. IP Relaxation: Relax IP restrictions
  5. Save

Prior to doing that, The curl command above failed. After making those changes, the curl command succeeded.

Also be aware the username is the username, not the email address. I made that mistake too.

Once you get the OAuth policies in SFDC set up, then the ServiceCallout should work. The configuration is pretty straightforward, something like this:

<ServiceCallout name='SC-1'>
  <Request>
    <Set>
     <Headers>
       <Header name='content-type'>application/x-www-form-urlencoded</Header>
     </Headers>
     <FormParams>
       <FormParam name='username'>{sfdc_username}</FormParam>
       <FormParam name='password'>{sfdc_password}</FormParam>
       <FormParam name='client_id'>{sfdc_client_id}</FormParam>
       <FormParam name='client_secret'>{sfdc_client_secret}</FormParam>
       <FormParam name='grant_type'>password</FormParam>
     </FormParams>
     <Verb>POST</Verb>
    </Set>
  </Request>
  <Response>tokenResponse</Response>
  <HTTPTargetConnection>
    <SSLInfo>
        <Enabled>true</Enabled>
        <IgnoreValidationErrors>false</IgnoreValidationErrors>
    </SSLInfo>
    <Properties>
      <Property name='success.codes'>2xx, 3xx</Property>
    </Properties>
    <URL>https://login.salesforce.com/services/oauth2/token</URL>
  </HTTPTargetConnection>
</ServiceCallout>

Then you can use ExtractVariables to get the token in the response. The response looks like:

{
  "access_token": "00D616nVW5x9v47IITEiRhYxCsxP8iOFtkhqd4Qvc6cQOkTVpx",
  "instance_url": "https://myhostname.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D61600AC/00562xbAnAAI",
  "token_type": "Bearer",
  "issued_at": "1520562770441",
  "signature": "e3pDLX1T64ofgp6YoC2stkOfXbbzexBNmnyklTeBxf0="
}

So you need an ExtractVariables like this:

<ExtractVariables name='EV-1'>
  <Source>tokenResponse</Source>
  <JSONPayload>
    <Variable name='sfdc_token'>
       <JSONPath>$.access_token</JSONPath>
    </Variable>
  </JSONPayload>
</ExtractVariables>

And then you can pass that sfdc_token back to salesforce in the target.

I'd suggest wrapping this thing in a cache that expires every... 60 minutes or so.

Finally, It's generally not a good idea to perform a "username login" from a service like Apigee Edge.

Just sayin. But you probably know what you're doing.

Hi Dino,

Thanks for the swift response!

I confirmed it with the Salesforce team, there are no IP address restrictions and the firewall is open for me to access the OAuth API. I was going to use the same flow to integrate Apigee with SFDC and then cache the access token but for some reason, as I mentioned earlier, I get the unsupported grant_type=password error.

Assign Message Policy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <AssignMessage name="getSfdcDealerCodes_AssignRoutingVariable" enabled="true" continueOnError="false" async="false"> <!-- Target System name --> <AssignVariable> <Name>capi.target.route.system</Name> <Value>SFDC_Dealer</Value> </AssignVariable> <!-- Target Service name --> <AssignVariable> <Name>capi.target.route.service</Name> <Value>SFDC_Dealer</Value> </AssignVariable> <!-- Target Action name --> <AssignVariable> <Name>capi.target.route.operation</Name> <Value>getSfdcDealerCodes</Value> </AssignVariable> <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables> <AssignTo type="request" transport="http" createNew="false">AccessToken</AssignTo> <DisplayName>getSfdcDealerCodes_AssignRoutingVariable</DisplayName> </AssignMessage>
Service Callout Policy 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout name="Service-Callout-SFDCAccessToken" enabled="true" continueOnError="false" async="false">
    <DisplayName>Service Callout-SFDCAccessToken</DisplayName>
    <Request variable="AccessToken" clearPayload="true">
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
        <Add>
            <Headers>
                <Header name="Content-Type">application/x-www-form-urlencoded</Header>
            </Headers>
            <FormParams>
                <FromParam name="grant_type">{AccessToken.header.grant_type}</FromParam>
                <FromParam name="client_id">{AccessToken.header.client_id}</FromParam>
                <FromParam name="client_secret">{AccessToken.header.client_secret}</FromParam>
                <FromParam name="username">{AccessToken.header.username}</FromParam>
                <FromParam name="password">{AccessToken.header.password}</FromParam>
            </FormParams>
            <Verb>POST</Verb>
        </Add>
    </Request>
    <Response>response</Response>
    <HTTPTargetConnection>
        <URL>https://{{host}}/oauth2/token</URL>
        <Properties/>
    </HTTPTargetConnection>
</ServiceCallout>
Proxy Endpoint

<Flow name="getSfdcDealerCodes">
            <!-- getSfdcDealerCodes Request Flow -->
            <Request>
                <!--  Extract path/query param variables -->
                <Step>
                    <Name>getSfdcDealerCodes_AssignRoutingVariable</Name>
                </Step>
                <Step>
                    <Name>Service-Callout-SFDCAccessToken</Name>
                </Step>
            </Request>
            <!-- getSfdcDealerCodes Response Flow -->
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/sfdc-dealer-codes") and (request.verb = "GET")</Condition>
        </Flow>

postman-error.jpg

Do let me know if I can provide you with more information.

Thanks in anticipation