Understanding the Conflict
When we integrate Salesforce with Apigee using the OAuth 2.0 JWT Bearer flow, we often encounter the frustrating unsupported_grant_type error. As Salesforce developers, we are accustomed to sending the grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer payload in a standard form-encoded body. However, when an architectural requirement forces the use of a custom x-access-token header or a specific Gateway configuration, Apigee’s standard OAuthV2 policy often fails to parse the incoming request correctly.
The error triggers because the Apigee OAuthV2 policy is strictly looking for the grant_type parameter within the body of the POST request. If your client sends the data in a header, or if the Apigee proxy is configured to strip form-encoded parameters in favor of header-based auth, the policy cannot determine how to handle the token exchange, defaulting to an unsupported state.
Anatomy of the JWT Request Failure
In a standard Salesforce-to-Apigee flow, the request usually looks like this:
POST /oauth/token HTTP/1.1
Host: your-org.apigee.net
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=eyJhbGci...[JWT_TOKEN]
When you introduce the x-access-token header, developers often attempt to pass the assertion there instead of the body. The Apigee policy, specifically the OAuthV2 operation GenerateAccessToken or ValidateAccessToken, is hard-wired to look for specific query parameters or form fields. If it doesn't find grant_type in the standard location, it doesn't just fail to authenticate; it marks the grant type itself as unsupported.
Correcting the Policy Flow
To resolve this, we need to intercept the request before it hits the OAuthV2 policy. We will use an AssignMessage policy to transform the incoming headers into the expected form parameters.
Step 1: Create an AssignMessage Policy
This policy will extract the token from your x-access-token header and map it to the assertion parameter expected by Apigee’s underlying flow.
<AssignMessage name="AM-MapHeaderToForm">
<AssignTo createNew="false" transport="http" type="request"/>
<Set>
<FormParams>
<FormParam name="grant_type">urn:ietf:params:oauth:grant-type:jwt-bearer</FormParam>
<FormParam name="assertion">{request.header.x-access-token}</FormParam>
</FormParams>
</Set>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
</AssignMessage>
Step 2: Update the Proxy Endpoint
In your Proxy Endpoint, ensure this policy executes before the OAuthV2 policy. This ensures that when the OAuth policy executes, the grant_type is physically present in the request body, regardless of how the client sent it.
<PreFlow name="PreFlow">
<Request>
<Step>
<Name>AM-MapHeaderToForm</Name>
</Step>
<Step>
<Name>OAuthV2-GenerateToken</Name>
</Step>
</Request>
</PreFlow>
Troubleshooting the OAuthV2 Policy Configuration
If the error persists after mapping the form parameters, check the OAuthV2 policy configuration itself. A common pitfall is the SupportedGrantTypes element.
Ensure that your policy explicitly allows the JWT bearer grant type. If this element is omitted or misconfigured, Apigee will reject the request by default.
<OAuthV2 name="OAuthV2-GenerateToken">
<Operation>GenerateAccessToken</Operation>
<SupportedGrantTypes>
<GrantType>urn:ietf:params:oauth:grant-type:jwt-bearer</GrantType>
</SupportedGrantTypes>
<GenerateResponse enabled="true"/>
</OAuthV2>
It is also vital to verify that the grant_type being sent matches the string defined in your SupportedGrantTypes exactly. Any variation in character case or whitespace will trigger the unsupported_grant_type error.
Best Practices for Secure JWT Handling
When working with JWTs in Apigee and Salesforce, avoid logging the full token in your Apigee Trace tool or system logs.
- Masking: Use an
AssignMessageorKeyValueMapto store sensitive secrets rather than hardcoding them in policies. - Validation: Always include a
VerifyJWTpolicy before theOAuthV2policy to ensure the token has not been tampered with and that theiss(issuer) matches your Salesforce Connected App consumer key. - Caching: Leverage Apigee’s internal caching to store the validated tokens to reduce the overhead of constant token validation against the identity provider.
Key Takeaways
- Header Mapping: Apigee’s OAuthV2 policy requires
grant_typein the request body; if your architecture uses headers, use anAssignMessagepolicy to transform headers into form parameters. - Explicit Support: Always define the
urn:ietf:params:oauth:grant-type:jwt-bearerstring within the<SupportedGrantTypes>block of your policy. - Order of Operations: Ensure your transformation policy runs in the
PreFlowbefore theOAuthV2policy is invoked. - Validation First: Validate the JWT signature using a dedicated
VerifyJWTpolicy before attempting to use the token to generate an access token, improving security and error visibility.
Leave a Comment