DEV Community

Julien Bourdeau for Algolia

Posted on • Originally published at algolia.com

API keys vs JWT authorization – Which is best?

As you build your own APIs, examining your use cases will help you decide which security methods to implement for each API. For some use cases, API keys are sufficient; in others, you’ll want the additional protection and flexibility that comes with JSON Web Tokens (JWT) authorization.

So in the comparison API keys versus JWT authorizations, the winner is .. it depends.

All API calls require some measure of security and access control. API keys with a sensible ACL can provide enough security without adding too much overhead. But with the increased use of microservices for nearly every small and large task, your API ecosystem may need a more unified, granular, and secure method like JWT authorization.

When API keys are fine

Online businesses that use a cloud-based Search API can normally expose read-only API keys without great risk – if the underlying index of data contains no secrets. In fact, client-side apps should connect to the cloud search engine directly for performance reasons – thereby exposing their API key – to avoid the lengthier trip to the back-end server before going to the cloud. On the other hand, index updates require restricted access API keys, which should never be exposed.

But in both use cases (search and indexing), API keys are generally fine; there’s no urgent need for the overhead of JWT authorization.

When it’s time to consider JWT authorization

Increasingly, however, APIs demand more flexibility and protection. JWT authorization not only adds an additional level of security (more about this below), it also provides a more manageable and easier method to orchestrate the multitude and network of APIs used on a daily basis. As described in the next sections, JWT centralizes authentication & authorization by generating a single shared token that contains user and app-level information (encrypted or hashed) to help any API in the same ecosystem determine what the token-holder is allowed to do.

API keys, at first glance, seem so simple – you just need to send the proper API key and you’re ready to go. But that’s a bit deceptive. When your ecosystem relies on many integrated microservices, managing numerous API keys becomes messy, unreliable, and nearly impossible to manage. They grow in number, they change, they expire, they get deleted, their ACLs change – all that and more, without notifying the apps and users relying on these same API keys.

With JWT, you lay the foundation for a single sign-on architecture. We discuss this below in the section Switching over to JWT.

Using API keys vs JWT authorization

Using Api Keys

API keys are direct, simple, and fully transparent. They don’t represent any underlying information, they do not encrypt a secret message. They are simply an unreadable unique id.

Here’s an example of a publicly available API key in client-side javascript. The code includes an App ID (app-id-BBRSSHR\) that uses the API key (temp-search-key-ere452sdaz56qsjh565d\) to allow it to search. The App ID refers to one of your user-facing apps (like an online website or streaming service). The API key is temporary and short-lived (expiring after some period of time) to provide some protection from unwanted use or abuse.

import { hitTemplate } from "./helpers";
const search = instantsearch({
  appId: "app-id-BBRSSHR",
  apiKey: "temp-search-key-ere452sdaz56qsjh565d",
  indexName: "demo\_ecommerce"
});
Enter fullscreen mode Exit fullscreen mode

Another example: Indexing, which requires a more secure API key. It has the same format (appId + apiKey), but it is private because it is hidden from the public, either in compiled code or a secure database on your back end. The App ID (YourApplicationID) refers to the back office system. The API key (YourAdminAPIKey) might be a permanent admin key that changes only once a year for simpler maintenance.

use Algolia\\AlgoliaSearch\\SearchClient;
$client = SearchClient::create(
    'YourApplicationID',
    'YourAdminAPIKey'
);
$index = $client->initIndex('demo\_ecommerce');
$index->saveObject(
  \[
    'firstname' => 'Jimmie',
    'lastname'  => 'Barninger',
    'city'      => 'New York',
    'objectID'  => 'myID'
  \]
);
Enter fullscreen mode Exit fullscreen mode

Using the JWT token

A JWT token is a large unreadable set of characters that contains hidden and encoded information, masked by a signature or encryption algorithm. It’s made up of three parts: a header, body, and signature. They are separated by a period: Header.Body.Signature.

EZPZAAdsqfqfzeezarEUARLEA.sqfdqsTIYfddhtreujhgGSFJ.fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd
Enter fullscreen mode Exit fullscreen mode

The JWT header is EZPZAAdsqfqfzeezarEUARLEA, which contains the following information:

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
Enter fullscreen mode Exit fullscreen mode

There are different algorithms available, for example RS256 and HS256. Here we use HS256 which requires a private key to be used when generating the signature. RS256\ uses both a private and a public key combination.

The JWT body (called the payload) is sqfdqsTIYfddhtreujhgGSFJ, which contains the user’s identity to help establish the token user’s rights. It also gives other information, such as an expiration date (the shorter the more secure):

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
Enter fullscreen mode Exit fullscreen mode

The signature is fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd, which is generated by combining the header, body, and a shared private key, using the HS256 hashing method, as indicated in the header.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
Enter fullscreen mode Exit fullscreen mode

And so that’s how you get the following token: Header.Body.Signature:

EZPZAAdsqfqfzeezarEUARLEA.sqfdqsTIYfddhtreujhgGSFJ.fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd
Enter fullscreen mode Exit fullscreen mode

A word about authentication & authorization

Both API key and JWT are used for authentication and authorization, but they do it differently.

  • Authentication allows the user or application to use one or more methods of the API.
  • Authorization defines how they can use those methods. Some apps or users can only read the data; others can update; others are administrators (roles and permissions). Same goes for API keys, as managed by their ACLS - they can be read-only, write-access, or admin.

API keys authenticate and authorize using the same API key. JWT Authorization requires an initial authentication process before it generates the authorization token. Once the token is generated, it is used across the ecosystem to determine what the token holder can and cannot do.

Additionally, API keys authenticate the application not the user; whereas, JWT authenticates both the user and the application. Of course, you could use API keys for user-level authorization, but it’s not well-designed for that - an ecosystem would need to generate and manage API keys for every user or session id, which is unnecessarily burdensome for a system.

A word about better protection and security

In terms of security, both API keys and JWT are open to attacks_._ The best security measure is to implement a secure architecture for all end-to-end communications.

That said, API keys are historically less secure because they rely on being hidden. You can hide keys with SSL/TLS/HTTPS, or by restricting their usage to back-end processes. However, you can’t control all API use; API keys are likely to leak; HTTPS is not always possible; and so on. With JWT, because the token is hashed / encrypted, it comes with a more secure methodology that is less likely to be exposed.

What information is in a JWT token?

The most notable difference between an API key and a JWT token is that JWT tokens are self-contained: they contain information an API needs to secure the transaction and determine the granularity of the token-holder’s rights. In contrast, API keys use their uniqueness to gain initial access; but then the API needs to find a key’s associated ACL in a central table to determine exactly what the key gives access to. Typically, the API key provides only application-level security, giving every user the same access; whereas the JWT token provides user-level access.

A JWT token can contain information like its expiration date and a user identifier to determine the rights of the user across the entire ecosystem.

Let’s take a look at some of the information you can include in a JWT token:

iss (issuer): identifies the principal that issued the JWT.
sub (subject): identifies the principal that is the subject of the JWT. Must be unique
aud (audience): identifies the recipients that the JWT is intended for (array of strings/uri)
exp (expiration time): identifies the expiration time (UTC Unix) after which you must no longer accept this token. It should be after the issued-at time.
nbf(not before): identifies the UTC Unix time before which the JWT must not be accepted
iat (issued at): identifies the UTC Unix time at which the JWT was issued
jti (JWT ID): provides a unique identifier for the JWT.
Enter fullscreen mode Exit fullscreen mode

Example

{
    "iss": "stackoverflow", 
    "sub": "joe", 
    "aud": \["all"\], 
    "iat": 1300819370, 
    "exp": 1300819380, 
    "jti": "3F2504E0-4F89-11D3-9A0C-0305E82C3301",
    "context": 
    { 
        "user": 
        { 
            "key": "joe", 
            "displayName": "Joe Smith" 
        }, 
        "roles":\["admin","finaluser"\] 
    } 
}
Enter fullscreen mode Exit fullscreen mode

JWT authorization offers flexibility, reliability, and more security

So here’s the scenario. You have many applications:

  • Apps that allow us to track the API usage of our all our users
  • Apps that give access to billing and customer data
  • Apps that allow API users to change settings on different systems
  • Apps that retrieve product data or business content
  • And other such use cases

Doing it with API keys

The problem arises when there are more APIs running the show. Where do you store the 100s+ of API keys needed for all this access? Managing too many API keys requires a table of API keys to be made available to all apps that run within the ecosystem.

Thus, every app in the ecosystem would have to be aware of the database. Each app would need to connect and read from that table. And some apps would be allowed to generate new keys or modify existing keys. All of which is fine, but becomes difficult to maintain. One way to manage this is to create an API that validates a key against this database. But for that, you’ll need a second authentication system to connect to this API.

Not only is retrieving the API keys cumbersome, but maintaining their duration and level of authorization becomes tedious. And not every app functions at the app level, they need user-level rights. Additionally, APIs can be used in ways that differ from system to system. Worse, different apps share API keys, and are therefore dependent on the different apps to maintain correct access-levels of the shared API keys.

Switching over to JWT

Any API that requires authentication can easily switch over to JWT’s authorization. With JWT authorization, you get a user-based authentication. Once the user is authenticated, the user gets a secure token that they can use on all systems. The management of the user (and therefore the token) is centralized. You set up access rights and you give each user different rights for each system. The JWT authorization endpoint authenticates the user and creates the token.

With that architecture, the next step is to create a single sign on into the full ecosystem and rely only on the token for rights. In the end, the simplest and most robust and manageable approach is to create this single endpoint dedicated to do both authentication & authorization, such that all other servers across the entire ecosystem can rely on that central point to authorize the API interactions between client and server.

Authentication and Authorization flow

In sum, sometimes JWT is absolutely needed and sometimes it’s overkill

As an application developer, my primary concern when building APIs is that they are used properly and provide the correct data and functionality. I therefore rely heavily on the advice of DevOps to suggest the best and most manageable security. But it’s not just about security. In a large ecosystem, we need a simple and robust way to access microservices across multiple systems and servers, which is best served by centralizing the authentication and authorization processes.

JWT is overkill in the following two situations:

  • A simple ecosystem with only a few APIs
  • Suppliers of third-party APIs.

Third-party APIs should be easy to use and fast to implement, with little upfront work when integrating. Additionally, you need to share the key used to sign the token. So it's best when you control both ends, which is unlikely in third-party sceneries. You also need to trust the implementation. for example an API could choose to ignore the expiration date or the NBF (“not before”).

JWT is not overkill when …

You’ll want to use JWT when multiple services need to communicate with each other over a vast network. Centralizing and securing those exchanges is crucial. It’s especially important when each network or app requires different levels of access based on the user, not just the app. It’s also important to have control over the traffic and to be able to prioritize network calls. Finally, you want the simple plug-and-play experience when adding new microservices or improving existing ones.

Top comments (0)