---
title: "Securing your API with API Keys & JSON Web Tokens (JWT)"
description: "Explore how to secure your API using both API Keys and JSON Web Tokens (JWT), understanding their benefits, challenges, and implementation strategies."
canonicalUrl: "https://zuplo.com/blog/2026/01/07/authentication-with-both-jwt-and-api-keys"
pageType: "blog"
date: "2026-01-07"
authors: "nate"
tags: "API Gateway, API Security"
image: "https://zuplo.com/og?text=Securing%20your%20API%20with%20API%20Keys%20%26%20JSON%20Web%20Tokens%20(JWT)"
---
There are countless debates on which authentication method is best for securing
your API - API Keys or JSON Web Tokens (JWT). The truth is that if you want to
provide the best experience for your users (be they internal, external, or both)
your API should support both. Allowing your users to choose the authentication
method that is best for their situation gives your API the best chance of being
successfully adopted by your customers. In this article, we'll explore the
benefits and challenges of using both API Keys and JWTs for authentication, and
how to implement them effectively in your API.

## API Keys

API Keys are the easiest way to secure your API. They are simple, unique
identifiers that are generated for each client and are used to authenticate
requests. API Keys are typically sent in the request header or as a query
parameter. API Key authentication is by far the easiest way for developers to
get started with your API, as it requires minimal setup and is straightforward
to use - no complicated OAuth flows to worry about, just create a key and make a
request.

## JSON Web Tokens (JWT)

JSON Web Tokens (JWT) are self-contained tokens that can be validated without
needing to query a database or call an authentication server. They contain
information about the client and are signed to ensure their integrity. JWTs also
typically have an expiration time - this can offer an added layer of security,
but adds to the complexity of calling your API.

## Using Both API Keys and JWT

The best APIs offer both API Keys and JWT authentication options. This allows
developers to choose the method that best suits their needs. For example, you
might use API Keys for simple applications or for internal use, while using JWTs
for more complex applications or for external clients that require more
security.

## Challenges of Using Both Methods

While using both API Keys and JWTs can provide flexibility, it can also
introduce challenges in terms of implementing and managing authentication logic.
With JWT tokens, the subject's identity is embedded directly in the token, but
with API Keys you need to look up the key in a database to determine the client.
This can lead to increased complexity in your authentication logic and may
require additional infrastructure to manage API Keys effectively.

### Identifying the User

When using both API Keys and JWTs, you'll end up with two different ways to
identify the user. With JWTs you can easily extract the user's identity from the
token, but with API Keys you need to call an external system to look up the key
and determine the user.

Doing this right can be tricky. You'll need to account for users having multiple
API keys so they can be rotated without downtime, store metadata with the keys
to determine permissions/scopes, expirations, and more. Most importantly, you'll
need to design this system so your API's logic doesn't need to know which
authentication method was used - it should just be able to identify the user and
their permissions regardless of how they authenticated.

### Scopes and Permissions

When authorizing users with JWT tokens, you typically authorize based on the
user's identity (i.e. you get the `sub` from the token and check if they have
access to the resource). If you use scopes in the JWT token, you can narrow the
access for that particular token (i.e. a token with `read:users` scope can only
read users, but not write them).

While you could just build a system where the API Key has all the permissions of
the user who created it, this isn't how your customers expect API Keys to work -
they expect to be able to create API Keys with specific permissions/scopes.
Often this is the entire point of API Keys - to allow users to create keys with
specific permissions that can be easily revoked if needed. This means you'll
need to build a system for managing all of this metadata for API Keys, and
ensure that your entire system can handle both types of authentication and
authorization.

<div class="mx-48">

![Github Token Management UI](/media/posts/2026-01-07-authentication-with-both-jwt-and-api-keys/image.png)

</div>

### Multiple Backends and Microservices

In my experience, the biggest challenge with implementing both API Keys and JWTs
in your API is that your backend often spans multiple services. Maybe you have a
Kubernetes cluster with multiple backends or you have a serverless architecture
with multiple functions. In either case, you need to ensure that all of your
services can handle both types of authentication and authorization. This can be
tricky, especially if you have different teams working on different services.
You'll need to ensure that all of your services are using the same
authentication and authorization logic, and that they can handle both API Keys
and JWTs seamlessly.

## Centralized Authentication

The obvious solution is centralization of authentication logic - let one service
handle authentication logic and then pass the user information to the other
services. This way your backend services don't need to worry about the details
of authentication and can just focus on their core logic. This allows a single
source of truth for logging, security patching, audit logs, and more. This is
the point where an API Gateway makes sense - a single entry point responsible
for authenticating all requests and securely passing user information to the
backend services in a consistent way.

## Implementing Both API Keys and JWTs with Zuplo

Zuplo's API Management platform is the best way to secure your API with both API
Keys and JWTs. This article will walk you through how to quickly implement both
authentication methods in your API without needing to make major changes to your
backend services.

### Create your Zuplo Project

There are multiple ways to create a Zuplo project - you can login to the
[Zuplo Portal](https://portal.zuplo.com) where you'll
[create and build your project](https://zuplo.com/docs/articles/step-1-setup-basic-gateway)
or you can use the [Zuplo CLI](https://zuplo.com/docs/cli/overview) to
[build your project locally and then deploy it to the cloud](https://zuplo.com/docs/articles/step-1-setup-basic-gateway-local).

This tutorial will start with the assumption that you have a Zuplo project
created and ready to go. If you don't, follow the links above to get started.

### Add API Key Authentication

To add API Key authentication to your Zuplo project, add the
[API Key Authentication Policy](https://zuplo.com/docs/policies/api-key-inbound)
to the route or routes you want to protect. This policy will authenticate
incoming requests based on the presence of a valid API Key in a request header
(typically the `Authorization` header). You can configure the policy to look for
the API Key in a different header or query parameter if needed.

When you add the policy to your route, you will change the default configuration
such that the `allowUnauthenticatedRequests` property is set to `true` - this
allows requests that don't have an API Key to be passed through to the next
policy in the chain, which will be the JWT Authentication policy.

![API Key Authentication Policy](/media/posts/2026-01-07-authentication-with-both-jwt-and-api-keys/image-1.png)

API Keys in Zuplo can contain metadata such as the user who created the key, the
scopes/permissions associated with the key, and more. This metadata can be used
to determine the user's identity and permissions when they authenticate with an
API Key. For example, if you wanted to store the same types of data as a JWT
token, you might set the metadata to the following:

```json
{
  "userId": "12345",
  "scopes": ["read:users", "write:users"]
}
```

### Add JWT Authentication

To add JWT authentication to your Zuplo project, add the
[JWT Authentication Policy](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound).
This policy will authenticate incoming requests based on the presence of a valid
JWT in the `Authorization` header. You can configure the policy to look for the
JWT in a different header or query parameter if needed.

:::info

Zuplo also offers policies specific for various providers such as
[Auth0](https://zuplo.com/docs/policies/auth0-jwt-auth-inbound),
[Clerk](https://zuplo.com/docs/policies/clerk-jwt-auth-inbound), and more. These
policies are built on top of the JWT Authentication Policy and provide
additional configuration options specific to those providers. If you're using
one of these providers, it's recommended to use the specific policy for that
provider.

:::

Just as you did with the API Key policy, you will change the default
configuration of the JWT Authentication policy such that the
`allowUnauthenticatedRequests` property is set to `true`.

![JWT Authentication Policy](/media/posts/2026-01-07-authentication-with-both-jwt-and-api-keys/image-2.png)

### Enforce Authentication

Now you have configured your API to allow both API Key and JWT authentication,
but you still need to enforce that one of these methods is used. By default all
Zuplo authentication policies prevent unauthenticated requests from being passed
to the next policy in the chain. By setting `allowUnauthenticatedRequests` to
`true` you have allowed unauthenticated requests to pass through, but you still
need to enforce that at least one of the authentication methods is used.

To enforce that at least one authentication method is used, you will add a
simple [Custom Policy](https://zuplo.com/docs/policies/custom-code-inbound) that
checks if the user information is present in the request context. If the user
information is not present, the policy will return a 401 Unauthorized response.
This policy should be added after both the API Key and JWT Authentication
policies in the policy chain.

More information on using multiple authentication policies can be found in the
[Zuplo documentation](https://zuplo.com/docs/articles/multiple-auth-policies).

Using Zuplo's programmability we can easily create a policy that enforces that
at least one authentication method is used. Here's an example of how you might
implement this policy:

```ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export default async function policy(
  request: ZuploRequest,
  context: ZuploContext,
) {
  if (!request.user) {
    return new Response("Unauthorized", { status: 401 });
  }
}
```

Additionally, you can also use this custom policy to map the API Key metadata to
the same format as the JWT Token so that later policies don't need to worry
about which authentication method was used. This can be done by accessing the
metadata on `request.user.data`.

```ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export default async function policy(
  request: ZuploRequest,
  context: ZuploContext,
) {
  if (!request.user) {
    return new Response("Unauthorized", { status: 401 });
  }

  if (request.user.data?.userId) {
    // User is authenticated with API Key, map the userId from the
    // API Key metadata to the sub so that it matches the JWT token format
    request.user.sub = request.user.data.userId;
  }
}
```

### Send User Information to Backend Services

Now that you have authentication setup on your API Gateway, you need to forward
the request on to your backend and include the user information in the request.
This allows your backend service to be agnostic to the authentication method
used and just focus on the core logic of the request.

To do this, you can either create a new custom policy or just set the header
from the existing custom policy that you created to enforce authentication. This
header can then be read by your backend service to determine the user's identity
and permissions.

```ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export default async function policy(
  request: ZuploRequest,
  context: ZuploContext,
) {
  if (!request.user) {
    return new Response("Unauthorized", { status: 401 });
  }

  if (request.user.data?.userId) {
    // User is authenticated with API Key, map the userId from the
    // API Key metadata to the sub so that it matches the JWT token format
    request.user.sub = request.user.data.userId;
  }

  // Set the user information in a header to be sent to the backend service
  request.headers.set("X-User-Id", request.user.sub);
}
```

## Conclusion

By supporting both API Keys and JWTs for authentication, you can provide a
flexible and secure API that meets the needs of a wide range of users. While
implementing both methods can introduce some complexity, using an API Gateway
like Zuplo can help you manage this complexity and ensure that your API is
secure and easy to use. With Zuplo's powerful policies and programmability, you
can easily implement both authentication methods and enforce that at least one
method is used for all requests.

## Next Steps

To start building your API with both API Keys and JWT authentication, sign up
for a [free account](https://portal.zuplo.com) and create your first Zuplo
project today.

Before you go to production, you'll want to make sure that you have secured your
backend to only accept requests from your API Gateway. There are many ways to do
this using Zuplo. For more information see the
[Zuplo documentation on securing your backend](https://zuplo.com/docs/articles/securing-your-backend).