ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop on the web portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
    How Zuplo WorksRequest LifecycleProject StructureAuthenticationUpstream CredentialsAPI KeysAPI ErrorsRoutingPolicy FundamentalsOpenAPIEnvironmentsSource Control and DeploymentDevelopment Options
Development
Policies
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
GraphQL
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsVersion Support PolicySecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Concepts

Upstream Credentials

Every request that passes through Zuplo involves two separate authentication decisions. The caller authenticates to the gateway, and the gateway authenticates to whatever it calls next. The two rarely use the same credential, and on most routes they don't even use the same scheme: a client might present a Zuplo API key while the upstream expects an AWS SigV4 signature, a Google ID token, or a vendor API key in a query string.

Authentication covers the first decision. This page covers the second — which credential the gateway attaches on the way out, which policy attaches it, and where the credential itself lives.

Two directions, one request

An upstream credential is attached in the middle of the request pipeline, after the caller's identity is known and before the request leaves for the origin.

Zuplo API Gateway
Verify caller credential
Authorize, rate limit, log
Attach upstream credential
Caller
Upstream
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

The ordering is what makes the pattern useful. By the time the credential is attached, request.user is populated, so the same request can be authorized with Require User Claims, rate limited per identity, and logged with the caller's subject — and the credential the upstream receives is one the caller never held.

That changes what a leaked caller credential is worth. A revoked gateway API key takes its holder's access with it, and the upstream secret exists in one place instead of one place per caller. It does not change what a caller with legitimate access can do. Deciding whether a call should happen is authorization, rate limiting, and logging — separate policies, on the same request.

Why every credential policy ends in -inbound

Zuplo's pipeline has exactly two policy stages. Inbound policies run before the handler sends the request upstream. Outbound policies run on the response, on its way back to the caller.

Attaching an upstream credential happens before the request leaves, so every credential-injection policy is an inbound policy and carries the -inbound suffix — including the ones whose entire purpose is the outbound call, like upstream-gcp-service-auth-inbound. The suffix names the pipeline stage, not the direction of the credential. It is not a typo.

The -outbound policies work on responses. That is where Secret Masking belongs: it redacts credentials that leaked into a response body before the caller — or an LLM reading the response — ever sees them.

Choosing a policy

The upstream expectsPolicyAvailabilityPick it when
A static key or token in one headerset-upstream-api-key-inboundAll plansOne secret, one header. The common case, and the default header is Authorization.
Several headers, or a value composed from more than one variableset-headers-inboundAll plansThe upstream wants a non-standard header name, two headers, or a key plus a tenant identifier.
A GCP ID token or a scoped Google access token, from a stored SA keyupstream-gcp-service-auth-inboundEnterpriseCloud Run, Cloud Functions, or GKE behind IAP (audience), or a Google API that needs OAuth scopes.
A GCP ID token, with no service-account key stored anywhereupstream-gcp-federated-auth-inboundEnterpriseThe same IAM-protected services, using Workload Identity Federation instead of a stored key. Audience-bound ID tokens only.
A self-signed JWT accepted by Cloud Endpoints or ESPv2upstream-gcp-jwt-inboundEnterpriseRarely. The policy's own page steers you to GCP service auth in most cases.
An access token from any OAuth 2.0 client-credentials endpointupstream-oauth-client-credentials-inboundAll plansAn internal authorization server, or any SaaS vendor that issues client-credentials tokens. Set tokenUrl, clientId, clientSecret, and scope or audience if the provider needs them.
An Entra ID (Azure AD) access token from the client-credentials flowupstream-azure-ad-service-auth-inboundEnterpriseAzure App Service, Azure Functions, or any Entra-protected resource. It builds the Entra token URL for you from a tenant id.
A SigV4-signed request, credentials from a stored IAM key pairupstream-aws-service-auth-inboundEnterprise · BetaLambda or another AWS service, optionally through an STS AssumeRole. Read AWS is different first.
A SigV4-signed request, with no AWS keys stored anywhereupstream-aws-federated-auth-inboundEnterprise · BetaThe same, using AssumeRoleWithWebIdentity instead of a stored key pair.
A Firebase admin tokenupstream-firebase-admin-auth-inboundAll plansFirestore or another Firebase service called with service-account permissions.
A Firebase token scoped to one end userupstream-firebase-user-auth-inboundAll plansFirebase should apply that user's own permissions. userId can be read from request.user.
A JWT proving the call came from your gatewayupstream-zuplo-jwt-auth-inboundEnterpriseYour own upstream, with no third-party identity provider in the picture. Claims can be copied from the caller.
Anything elsecustom-code-inboundAll plansQuery-string keys, HMAC signatures, or picking the credential from the caller's identity.

Enterprise policies are free to try in development

The GCP, Azure AD, AWS, and Zuplo-JWT upstream policies above are Enterprise features for production traffic, and free to use on any plan for development. The two AWS policies are also in beta and may change in non-backward-compatible ways. The same applies to two policies this pattern often pairs with: audit-log-inbound and complex-rate-limit-inbound are Enterprise, while rate-limit-inbound and quota-inbound are not.

Two policies in the catalog look like they belong in the table and don't. set-headers-outbound sets headers on the response, so it can't authenticate an upstream call. custom-code-outbound runs after the response arrives — useful for reshaping a body, not for signing a request.

AWS is different

The two AWS policies do not set an Authorization header. SigV4 signs the exact final request — method, path, query, headers, body hash — so a header computed earlier in the pipeline would be wrong by the time the request left.

Instead, both policies resolve AWS credentials and register them on the request context. Something later in the pipeline signs with them:

  • The AWS Lambda handler, which does it for you.
  • Your own code, via AwsClient.fromContext(context) in a custom code policy or custom handler.

If a route uses one of these policies and the upstream returns 403, check that something is actually consuming the resolved credentials. The policy alone does not change the outgoing request.

MCP routes

MCP routes have their own credential broker. mcp-token-exchange-inbound resolves gateway-managed upstream credentials in three modes: user-oauth (per-user OAuth federation, tokens stored encrypted at rest and keyed to the user's subject), shared-oauth (one gateway-wide grant an administrator connects once), and id-jag (Cross-App Access token exchange, built on an active IETF draft rather than a finalized RFC). Per-user upstream API keys are not part of this set.

See Per-user OAuth to upstream MCP servers for how the outbound surface works, and Connect an upstream that uses an API key for upstreams with no OAuth at all.

Where the credential lives

Upstream secrets belong in environment variables, referenced from policy configuration with $env(VAR_NAME) and from code with environment.VAR_NAME. Values are encrypted at rest; values marked as secrets are write-only, so they can't be read back after they're set.

Two behaviors surprise people:

  • A changed value needs a new deployment. Variables are applied to an environment at deploy time. Updating a credential without redeploying leaves the old value in service.
  • The CLI and the Developer API set variables per branch. zuplo variable create and zuplo variable update both take a --branch flag and create a record for that branch alone.

Set shared credentials in the Portal, not the CLI

There is no all-environments assignment in the CLI or the Developer API. Only the environment-variable editor in the Zuplo Portal can apply one variable to several environments at once — open Settings → Environment Variables in your project and select every environment the credential belongs to.

A credential seeded per branch looks correct in the environment where it was created and is simply absent everywhere else. Nothing fails at build or deploy time; the first sign of trouble is a 401 or 403 from the upstream on live traffic, which can go unnoticed for days on a low-volume route.

The strongest option is to store nothing. Both federated policies (GCP, AWS) exchange the gateway's own OIDC identity for short-lived cloud credentials, so there is no long-lived key to leak, rotate, or seed into an environment.

Related

  • Securing your backend — the six ways to make sure only your gateway can reach your origin.
  • Secure a GCP backend with Zuplo upstream auth — an end-to-end walkthrough with GKE and Identity-Aware Proxy.
  • Authentication — the inbound half of the request.
  • Environment variables — syntax, environments, and deployment behavior.
  • Policies — how the inbound and outbound stages fit together.
Edit this page
Last modified on July 29, 2026
AuthenticationAPI Keys
On this page
  • Two directions, one request
  • Why every credential policy ends in -inbound
  • Choosing a policy
    • AWS is different
    • MCP routes
  • Where the credential lives
  • Related