Blog

API keys and JWTs: stronger together

·4 min read

They solve different problems

API keys answer: “was this caller provisioned to use this gateway?” They’re opaque, fast to verify, easy to revoke, and carry no embedded claims.

JWTs answer: “who is this caller, what are they allowed to do, and when does this assertion expire?” They’re self-describing, claims-bearing, and time-limited.

The common pattern of choosing one or the other misses the opportunity to use each for what it’s actually good at. In a well-structured gateway configuration, you want both:

  • An API key (or proxy credential) that authenticates the integration itself to the gateway.
  • A JWT that carries the identity and permissions of the human or service behind that integration.

The RequestRocket model supports this naturally

RequestRocket’s credential model has two types: proxy credentials (what callers use to authenticate to the gateway) and target credentials (what the gateway uses to authenticate to the upstream API).

The proxy credential type jwtVerify validates an incoming JWT. But you can also require a proxy-level API key alongside JWT validation by layering rules that check for the presence of both.

Pattern 1: API key on the proxy credential, JWT in a header check

The proxy credential authenticates the integration (API key); a rule checks that a caller-supplied X-User-Auth header — a JWT from your own identity provider — is present for user-context operations:

POST /clients/{clientId}/credentials
{
  "credentialType": "proxy",
  "credentialAuthType": "key",
  "credentialName": "mobile-app-backend",
  "credentialRegion": "us-east-1",
  "credentialSecret": {
    "key": "rr_live_xxxxxxxxxxxxxx"
  }
}

Then add a rule requiring the JWT header for user-specific endpoints:

POST /clients/{clientId}/proxies/{proxyId}/rules
{
  "effect": "allow",
  "methods": ["GET", "POST"],
  "path": {
    "path": { "pattern": "^/v1/user/" },
    "presence": "must_exist"
  },
  "headers": [
    {
      "name": { "pattern": "^X-User-Auth$" },
      "value": { "pattern": ".*" },
      "presence": "must_exist"
    }
  ],
  "priority": 10,
  "notes": "User-scoped endpoints: require X-User-Auth header"
}

Pattern 2: jwtVerify proxy credential with additional API key header check

For integrations where the caller should present both a valid JWT (verified against JWKS) and a known API key (as an additional proof of integration identity):

POST /clients/{clientId}/credentials
{
  "credentialType": "proxy",
  "credentialAuthType": "jwtVerify",
  "credentialName": "partner-api-jwt-verify",
  "credentialRegion": "us-east-1",
  "credentialSecret": {
    "jwksUri": "https://partner-auth.example.com/.well-known/jwks.json",
    "audience": "https://api.myapp.com",
    "issuer": "https://partner-auth.example.com/"
  }
}

Then add a rule that also requires the partner’s static API key in a custom header:

POST /clients/{clientId}/proxies/{proxyId}/rules
{
  "effect": "allow",
  "methods": ["GET", "POST"],
  "path": {
    "path": { "pattern": ".*" },
    "presence": "must_exist"
  },
  "headers": [
    {
      "name": { "pattern": "^X-Partner-Key$" },
      "value": { "pattern": "^partner_key_xxxxxxxxxxxx$" },
      "presence": "must_exist"
    }
  ],
  "priority": 5,
  "notes": "Partner integration: require static partner key alongside JWT"
}

Now a request must: (a) carry a valid JWT verified against the partner’s JWKS, AND (b) include the expected static partner key. Compromising one factor alone doesn’t grant access.

Using JWT claims to augment API key access

Even when your proxy credential is a simple key type (no JWT verification), you can pass a JWT in a header and use the filter token system to make decisions based on its claims — provided you’ve also attached a jwtVerify credential somewhere in the flow, or you’re passing the token as additional context.

A more common pattern: use the JWT claims to vary what the API key-authenticated caller can access:

POST /clients/{clientId}/proxies/{proxyId}/filters
{
  "token": [
    {
      "claim": { "pattern": "^tier$" },
      "value": { "pattern": "^(premium|enterprise)$" },
      "presence": "must_absent"
    }
  ],
  "requestPath": {
    "path": { "pattern": "^/v1/export" },
    "presence": "must_exist"
  },
  "operations": [
    {
      "effect": "destroy",
      "jsonPath": { "pattern": ".*" },
      "notes": "Block export endpoint — premium or enterprise tier required in JWT"
    }
  ],
  "notes": "Tier gate: export requires premium/enterprise claim in JWT"
}

The upstream side: key to the vendor, JWT to your backend

A common full-stack configuration looks like this:

LayerAuth mechanismRequestRocket role
Caller → GatewayjwtVerify proxy credentialValidates JWT, checks claims
Gateway → Upstream vendor APIbearer or key target credentialInjects vendor token
Gateway → Your own backendbasic or key target credentialInjects service account key

The caller authenticates with a JWT (issued by your identity provider). The upstream APIs receive their own credentials, injected by the gateway. Your backend receives the forwarded request with the caller’s JWT claims available in headers — without needing to trust the caller or re-verify the token.

When layering isn’t necessary

Not every proxy needs both mechanisms. Internal service-to-service calls between trusted services on your own infrastructure usually don’t need JWT claim checking — a proxy API key is sufficient. Save the JWT claim layer for:

  • Public-facing API endpoints where caller identity matters.
  • Partner integrations where you need to verify both integration identity (key) and user identity (JWT).
  • Multi-tenant APIs where JWT tenant claims drive access control.

Next steps

Both credential types and the full filter rule system are available on every proxy. Read the credential and filter documentation to see how to configure layered auth for your specific integration pattern, or start for free.

Enhance ISO 27001
Enhance SOC 2
Enhance GDPR
Enhance HIPAA

Add outbound API security
without changing code

Start on your own or talk to our team about improving the security of every API call you make.