Research details
Introduction
An Apigee proxy sits between clients and a backend, so its configuration determines which requests reach the service and what data is exposed along the way. A policy can be present without providing the intended protection: certificate validation might be disabled, an input check might cover only the body, or a shared rate limit might block unrelated users.
These five areas provide a starting point for reviewing a proxy. Each section explains what the configuration is meant to do and what to check around it, with examples for TLS, input validation, data handling, rate limiting, and credentials.
Step 1: Configure TLS and Validate Backend Certificates
API keys, tokens, and user data need protection while moving between clients, proxies, and backend services. TLS protects network traffic against interception and tampering, but the proxy also needs to validate the server certificate. Ignoring validation errors weakens that protection even when the connection uses HTTPS.
Configure HTTPTargetConnection and ServiceCallout to use TLSv1.2 or TLSv1.3 with a TrustStore:
<SSLInfo>
<Enabled>true</Enabled>
<IgnoreValidationErrors>false</IgnoreValidationErrors>
<TrustStore>ref://TrustStore</TrustStore>
<Protocols>
<Protocol>TLSv1.2</Protocol>
<Protocol>TLSv1.3</Protocol>
</Protocols>
</SSLInfo>Here, Enabled turns on TLS and IgnoreValidationErrors=false makes certificate validation failures fail the connection. TrustStore identifies the certificates trusted for validation, while Protocols restricts the connection to TLS 1.2 and TLS 1.3 rather than older protocols such as SSLv3 or TLSv1.0.
Check these settings on each relevant backend connection. A secure client-to-proxy connection does not protect a separate proxy-to-backend connection that has been configured differently.
Step 2: Validate Inputs Before Using Them
Input validation establishes what the endpoint accepts before untrusted values reach backend processing. That includes the expected structure and values, as well as limits on size and complexity. Oversized or deeply nested payloads can consume resources even when they are otherwise valid JSON or XML.
JSONThreatProtection and XMLThreatProtection apply limits to structured payloads. This JSONThreatProtection example sets bounds for arrays, nesting depth, object entries, entry names, and string values:
<JSONThreatProtection continueOnError="false" enabled="true" name="JSON-Threat-Protection">
<DisplayName>JSONThreatProtection</DisplayName>
<ArrayElementCount>16</ArrayElementCount>
<ContainerDepth>4</ContainerDepth>
<ObjectEntryCount>8</ObjectEntryCount>
<ObjectEntryNameLength>32</ObjectEntryNameLength>
<Source>request</Source>
<StringValueLength>32</StringValueLength>
</JSONThreatProtection>Those limits need to fit the API’s expected requests. They do not replace checks on individual fields, and they do not cover every place where a client can supply a value. Path parameters, query parameters, and headers also need validation before they are used.
For a user-id header expected to contain a UUID, first check that the value exists, then check its format. The following JavaScript example separates those two conditions:
// Regular expression for UUID validation
var userIdRegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
// Get the user-id from the request header
var userId = context.getVariable('request.header.user-id');
// Check if the user-id is defined
if (userId === undefined || userId === null) {
throw new Error("User-id header is missing");
}
// Validate user-id against the regular expression
if (!userIdRegExp.test(userId)) {
throw new Error("Invalid user-id format");
}
// If valid, proceed with other logicThis rejects missing and incorrectly formatted identifiers before later logic uses them. The same principle applies to other inputs: define the values the endpoint expects and check them at the point where they cross into processing. Length and complexity limits reduce backend work on oversized requests, while field validation helps prevent unexpected data from changing how a query or operation is interpreted.
Step 3: Limit Exposure of Sensitive Data
Sensitive data can appear outside the API response itself. URLs, logs, and debugging tools may retain API keys, passwords, tokens, or personal information, sometimes with broader access than the service that handles them. TLS protects data in transit; it does not decide which systems record it.
Keep Credentials Out of URLs
Accept credentials in headers or the request body rather than URL parameters. URLs may be recorded in server logs, browser history, and intermediary systems. Encrypting the network connection does not remove those records or control who can read them.
If an existing integration requires sensitive data in the URL, moving it into a header at the proxy reduces exposure in subsequent systems. It cannot remove copies already recorded before the request reached Apigee, so this is a limited mitigation rather than a substitute for changing the client.
The following AssignMessage example takes a user ID from a query parameter, assigns it to a header, and removes the parameter before forwarding the request:
<AssignMessage>
<AssignVariable>
<Name>request.header.X-User-Id</Name>
<Ref>request.queryparam.user_id</Ref>
</AssignVariable>
<Remove>
<QueryParams>
<QueryParam name="user_id"/>
</QueryParams>
</Remove>
</AssignMessage>The backend then receives the value outside the URL. Its logging and data-handling rules still matter: changing the field’s location does not make the value safe to log everywhere.
Mask Sensitive Data in Trace
Trace (Debug) exposes flow data while troubleshooting calls, which can include the same credentials and personal information the proxy is meant to protect. Configure data masking for sensitive fields, or use the private. prefix for custom variables that should stay hidden from diagnostic output. Review what Trace actually displays rather than assuming that a field is concealed because it is sensitive.
Step 4: Set Rate Limits at the Right Scope
Rate limiting helps prevent one caller from consuming too much backend capacity and reduces the impact of traffic bursts. In Apigee, SpikeArrest controls request rates. This example configures a rate of 100pm and uses the API key to identify the caller:
<SpikeArrest continueOnError="false" enabled="true" name="SpikeArrest-Policy">
<DisplayName>Spike Arrest</DisplayName>
<Rate>100pm</Rate> <!-- Limits to 100 requests per minute -->
<Identifier ref="request.header.x-api-key"/> <!-- Uses API key as the identifier -->
</SpikeArrest>The Identifier determines which requests share the limit, so it deserves as much attention as the Rate. A value that groups many unrelated users can cause them to compete for the same allowance. For example, an application identifier shared by all end users may throttle the whole application when only some users are generating heavy traffic.
That can turn a protection against excessive traffic into a source of disruption. Choose the identifier according to how clients use the API, and set a rate the backend can handle. Rate limiting helps manage load and abuse, but its effect depends on both the configured rate and how requests are grouped.
Step 5: Separate Authentication from Authorization
Authentication establishes who is calling; authorization determines what that caller is permitted to access. Checking a credential is only part of the decision. A valid key or token should not automatically grant access to every resource exposed by the proxy.
Apigee supports OAuth 2.0, which allows clients to access APIs on a user’s behalf without receiving the user’s password. Token lifetimes need a deliberate choice. If an access token is compromised, a longer lifetime gives more time to use it, so setting a very long expiration simply for convenience increases the exposure.
API keys can suit simpler client-access requirements. Manage them with rotation, rate limits, and quotas rather than treating a valid key as the end of the review. The following policy validates a key supplied in a request header:
<VerifyAPIKey name="VerifyAPIKey">
<APIKey ref="request.header.x-api-key" />
</VerifyAPIKey>Once the credential has been checked, the flow still needs to enforce the permissions appropriate to the requested resource. Review where that decision happens and whether any alternate response path bypasses it.
These configurations work together. TLS protects the connection, validation constrains what enters processing, masking reduces what diagnostics expose, and access checks determine which caller receives which data. Following the request through the actual flow is how to establish whether those protections run where they are needed.
Select text to leave a comment.