What is a reverse proxy
A reverse proxy is a server that sits between a caller and an upstream service. The caller sends its request to the proxy. The proxy inspects it, applies whatever logic it needs to, then opens a second request to the upstream on the caller’s behalf. The upstream only ever talks to the proxy – not directly to the caller.
Caller Proxy Upstream
|-- request ------------->| |
| method, path, headers | check credential |
| | evaluate rules |
| |-- forwarded request ------->|
| |<-- response ----------------|
|<-- response ------------| |Two hops, one caller-visible URL. The caller knows the proxy address. It does not know – and should not know – the upstream address.
Reverse proxies are used for many things: load balancing traffic across backend nodes, terminating TLS at the edge, caching responses, rewriting paths, and enforcing rate limits. Authentication is one more job the proxy can own, and it is often the most valuable one.
What makes it an authentication reverse proxy
An authentication reverse proxy has one job on top of forwarding: it authenticates the inbound request and attaches the correct credential on the outbound request.
That sounds simple. What it means in practice is that the upstream secret never leaves the proxy. The caller presents whatever credential the proxy accepts – an API key, a bearer token, a JWT. If that credential is valid, the proxy forwards the request to the upstream with its own credential attached. The caller cannot see the upstream credential. The upstream cannot see the caller’s credential.
This split matters most in three situations:
The upstream uses a credential type the caller cannot or should not hold. OAuth2 APIs issue client ids and secrets to machine clients. Those secrets are powerful: they let anyone who holds them mint tokens. The fewer places those secrets live, the smaller the blast radius if one leaks. An authentication reverse proxy collapses that to one.
You cannot modify the upstream. Putting authentication middleware inside a legacy API or a third-party service is usually not an option. The proxy adds the auth layer without touching the upstream at all.
Auth translation. The caller speaks API key. The upstream speaks OAuth2. Without a proxy, someone has to bridge that gap in application code, in every service that calls the upstream. With a proxy, it is one record in one place. The application code sends the same key every time; the proxy does the token exchange.
The two-credential model
RequestRocket implements an authentication reverse proxy with two credentials per proxy:
- A proxy credential (
credentialType: "proxy") defines what the caller must present to get through. This is the inbound check. - A target credential (
credentialType: "target") defines what the gateway attaches when it calls the upstream. This is the outbound auth.
Caller --[proxy credential]--> RequestRocket proxy --[target credential]--> Upstream API
(inbound: API key, (outbound: OAuth2 client
bearer, or JWT) credentials, API key, etc.)Neither credential is visible to the other side. The caller’s key does not reach the upstream. The upstream client secret does not reach the caller.
RequestRocket is not an authorization server for your callers. It does not issue their tokens, and it does not run their login. It checks the credential they present, then runs the upstream grant on their behalf.
This guide builds the most common pattern: callers authenticate with an API key; the upstream is an OAuth2 API using the client credentials grant. The last section shows how to swap the inbound credential to JWT verification if your callers already carry access tokens from an identity provider.
Building the proxy: prerequisites
You need:
- A RequestRocket client and one region. The target, both credentials, and the proxy must all share the same region.
- The upstream base URL (the resource server, not the token endpoint).
- An OAuth2 client registered for the client credentials grant: client id, client secret, token URL, and any scopes the vendor requires.
The examples below use a generic vendor at https://api.vendor.example. Swap in the real host. The shapes are the same for any API that accepts client credentials.
Four objects to create, in order: target → inbound credential → outbound credential → proxy.
Step 1: Create the target
The target is only the upstream base URL. It does not hold the secret.
POST /clients/{clientId}/targets
{
"targetName": "vendor-api",
"targetBaseURL": "https://api.vendor.example",
"targetTestPath": "/health",
"targetRegion": "us-east-1"
}Save the target id. targetTestPath is optional; use a cheap path if the vendor has one.
Step 2: Create the inbound proxy credential
Callers authenticate to the gateway with this credential. Inbound API keys are checked against the Authorization header only. Setting key to X-API-Key will not match. The header name, the secret, and addToHeader are all required.
POST /clients/{clientId}/credentials
{
"credentialType": "proxy",
"credentialAuthType": "key",
"credentialName": "caller-api-key",
"credentialRegion": "us-east-1",
"credentialSecret": {
"key": "Authorization",
"value": "rr_live_replace_with_a_long_random_secret",
"addToHeader": true
}
}Give each app or agent its own proxy credential if you want to revoke one without rotating the others. The value above is a placeholder – generate a long random string and store it where that caller can read it. You will not be able to read the secret back after create.
A static bearer token is the same idea with credentialAuthType: "bearer" and a token field. Use a key when you want a credential you issued. Use JWT verification, covered later, when an identity provider already issued the caller’s token. A longer comparison of inbound types is in API authentication methods.
Step 3: Create the outbound OAuth2 credential
This credential is target, not proxy. The gateway uses it to call the vendor token endpoint. Required secret fields for client credentials are appClientId, grantType, clientSecret, accessTokenURL, and addToHeader.
POST /clients/{clientId}/credentials
{
"credentialType": "target",
"credentialAuthType": "oauth2",
"credentialName": "vendor-oauth2",
"credentialRegion": "us-east-1",
"credentialSecret": {
"grantType": "client_credentials",
"appClientId": "your-vendor-client-id",
"clientSecret": "your-vendor-client-secret",
"accessTokenURL": "https://auth.vendor.example/oauth/token",
"scope": "accounts.read invoices.read",
"addToHeader": true,
"clientAuthInHeader": true
}
}scope is optional. Send it when the vendor’s token endpoint requires a scope, as a space-separated string.
clientAuthInHeader controls how the client id and secret are presented to the token endpoint, not to the resource API. true sends them as Authorization: Basic. Omit it or set false to send them in the token-request body. Vendors disagree, and the wrong choice is the usual 401 while the resource URL is still fine. Check the vendor’s token docs before you create the credential: the secret cannot be edited afterward. Rotation means a new credential, then pointing the proxy at it.
addToHeader: true puts the access token on the upstream request as Authorization: Bearer <token> (the default prefix is Bearer). That overwrites whatever the caller sent. The caller key never arrives at the vendor.
There is no authorize URL and no redirect for this grant. The gateway fetches a token on use and refreshes it when it expires. You do not paste an access token into the secret.
Step 4: Create the proxy and an allow rule
Wire the three ids together. proxyDefaultRuleEffect: "deny" means a request that matches no allow rule is refused, even if the caller authenticated.
POST /clients/{clientId}/proxies
{
"proxyName": "vendor-auth-proxy",
"proxyRegion": "us-east-1",
"proxyProxyCredentialId": "<caller-api-key-id>",
"proxyTargetId": "<vendor-api-target-id>",
"proxyTargetCredentialId": "<vendor-oauth2-id>",
"proxyDefaultRuleEffect": "deny",
"proxyAlias": "vendor-api"
}proxyName, proxyRegion, and the three ids are required. proxyAlias is optional (lowercase letters and dashes).
Then allow only the paths this caller should reach:
POST /clients/{clientId}/proxies/{proxyId}/rules
{
"effect": "allow",
"ruleActive": true,
"methods": ["GET"],
"path": {
"path": { "pattern": "^/v1/(accounts|invoices)(/.*)?$" },
"presence": "must_exist"
},
"priority": 10,
"notes": "Read accounts and invoices only"
}effect and ruleActive are required. Anything outside that pattern, including POST and DELETE, hits the default deny. Add another allow rule if a second method is actually required. Do not start from proxyDefaultRuleEffect: "allow" and hope to tighten it later – an agent will find the path you forgot.
Step 5: Call the proxy
The proxy URL is regional. After creation, copy the base URL from the proxy details. It looks like this:
https://{region}.requestrocket.io/api/{proxyId}Call it with the inbound key. The path after the proxy id is the path on the target.
curl -X GET "https://us-east-1.requestrocket.io/api/<proxyId>/v1/accounts" \
-H "Authorization: rr_live_replace_with_a_long_random_secret"A missing or wrong key is rejected at the proxy. A valid key on a path the rule does not allow is rejected by the rule. A valid key on an allowed path is forwarded with the OAuth2 access token. The caller does not see the client secret, the token endpoint, or the refresh.
What the reverse proxy gives you
The OAuth client secret stays in one record. Apps and agents hold the proxy key. They cannot mint vendor tokens if that key leaks, and they cannot read the client secret back from the API.
Callers do not change when the vendor secret rotates. Create a new target credential with the new client secret and set proxyTargetCredentialId on the proxy. The proxy URL and the inbound key stay put.
The allow-list is the privilege boundary. Method and path rules are evaluated on every call, outside the caller. That is the control you cannot add to an API you do not own.
Volume is a separate grant. A proxy with no meters has no rate limit. To cap calls, create a meter on the proxy (POST /clients/{clientId}/proxies/{proxyId}/meters) with meterType: "request_count", meterActive: true, and limits. Client configuration defaults do not enforce limits on a proxy that already exists.
Every forwarded call is logged. When the vendor returns 401 or 429, the request log is the place to see status and timing without adding instrumentation to the caller.
Variant: callers with IdP-issued JWTs
The inbound and outbound credentials are independent. You can change either without changing the other.
If callers already carry access tokens from Auth0, Okta, Cognito, or another OIDC provider, keep the target and the OAuth2 credential exactly as they are. Replace only the proxy credential with jwtVerify. The gateway checks the signature, expiry, and the issuer and audience you configure. It does not compare the token to a stored string – it verifies it cryptographically against the issuer’s public keys.
POST /clients/{clientId}/credentials
{
"credentialType": "proxy",
"credentialAuthType": "jwtVerify",
"credentialName": "idp-jwt-verify",
"credentialRegion": "us-east-1",
"credentialSecret": {
"algorithms": ["RS256"],
"jwksUri": "https://YOUR_TENANT.example.com/.well-known/jwks.json",
"issuer": "https://YOUR_TENANT.example.com/",
"audience": "https://api.vendor.example"
}
}algorithms is required. Supply exactly one of jwksUri (asymmetric keys) or sharedSecret (HMAC). Point proxyProxyCredentialId at this credential instead of the API key. Callers then send Authorization: Bearer <access token>. The outbound side is unchanged: the vendor still receives the client-credentials access token, not the caller’s JWT.
The full Auth0 registration, audience, and issuer checklist is in Add Auth0 JWT authentication to any API. Do not set the proxy credential’s credentialAuthType to oauth2 for this. That type is for the target credential that talks to the vendor token endpoint.
Next steps
Create the target, the two credentials, and the proxy in the order above, then confirm a denied path and an allowed path before you hand the proxy URL to an agent. Credential fields and the proxy URL are in the credentials guide and the proxies guide.