JWT (pronounced “jot”) provides both authentication (verify identity & password) and authorization (permissions). Below, auth refers to both functions unless otherwise stated.

Before diving into JWT, its important to go over Session Auth and see what it has to offer. Following that it will be easier to see how JWT changes things up.

Session Auth

Session-based auth is simple. Once a user logs in with the correct credentials he is issued a Session ID which the auth server also saves in its database (centralized) along with the User ID. The Session ID can be a randomly generated GUID and is included in an HTTP header on every client request. The server then extracts it from the request and checks if the Session ID exists in its database (stateful) to authenticate the user.

The server then also checks if the user’s request is authorized by looking up the permissions for the user stored in the database (more statefuleness). The data in the tables may look like this:

SESSIONS
UserID    SessionID            CreatedAt
1283      9a0cas0dc80ac0das8   2019-10-22-07:43:59
1392      caoc9a8cacma2cajoa   2019-10-22-05:21:02

AUTHORIZATIONS
UserID    Authorizations
1283      {allowed:true, manager:true}

The client (browser or app) typically stores the Sesssion ID in browser cookies or similar local storage. A simple auth check may look like this:

// Authentication
if (DB.Sessions.findSessionId(request.cookies.sessionId)) {
    // Authorization
    if (DB.Authorizations.findUserId(1283).manager) {
        // Allow action
    }
}

Another benefit of Session Auth when it comes to browsers in particular is that cookies have some inherent client-side checks such as same-origin, domain/sub-domain scopes, automatically appending of cookies to header, and httpOnly server-side cookies. This only helps with browser-based clients though and needs some more plumbing on other clients such as apps and servers.

As we’ll see, unlike JWT, Session Auth benefits from simplicity. Also unlike JWT, as we’ll see shortly, the changes to user auth take effect immediately. However, JWT has some compelling use-cases, such as it’s passport-like nature and standardization.

Why JWT Auth?

The key architectural difference here is that JWT stores the authorizations – or claims as they are called in JWT parlance – on the client instead of the server. This makes it stateless just like HTTP itself.

JWT is like a driving licence or passport, which you keep with you at all times. If you go to an 18+ bar, you can present it as verification to claim your identity (authentication) and age (authorization). The gatekeeper will trust the age on your license or passport and let you through.

Session-auth on the other hand is analogous to scanning your key-card when entering your corporate office building. The key card scanner checks in the cards database to determine if your card is still valid and if you are allowed to enter that particular building, after which it unlocks the door.

The differences here should become clear. The driving license is more portable and can be used across hundreds of establishments (horizontal scale). A key card would not work across hundreds of establishments which may or may not have the scanner installed. Those establishments will also need network connectivity and a proprietary scanner.

This is why Facebook Login or any of the other token-based providers can scale across millions of websites (and why Microsoft’s version is aptly called Passport). They are issuing a passport of sorts, which they try and keep secure and up-to-date.

If you have a dozen APIs or microservices, then your JWT passport can work with them fairly easily. You don’t have to deal with the latency that comes with every microservice asking your auth server to verify the incoming request. The microservice can do that independently, without having access to the auth server or your database. This is the key selling-point for JWT.

Another key benefit is that unlike Session Auth where everyone is rolling their own version, JWT is standardized and has seen strong adoption. This means there is an abundance of libraries and integrations for your preferred server, apps and website environments. There are many notable auth-as-a-service providers as well that offer JWT by default. This results in many startups having one less thing to deal with.

Reviewing Notable Differences

Let’s review the key differences:

  1. Session Auth is centralized, JWT is distributed by nature.

  2. Session Auth stores authorization data on the server, JWT stores it on the client (hence distributed).

  3. Session Auth is stateful, JWT is stateless.

  4. Session Auth is realtime, JWT can take a few minutes to sync new permissions!

  5. Session Auth is very simple to understand, JWT has a bunch of concepts that can trip up even seasoned developers.

JWT Auth Flow

A user logs in to your auth server, which issues an Access Token to the user. This Access Token acts as a passport to all your services the client will need to access. This Access Token contains claims similar to what we stored in our database for Session Auth:

{
  user_id: 1283
  exp: <expiry>
  authorizations: {
    allowed: true,
    manager: true,
  }
}

Every request a client makes to your service, will contain this Access Token in the requests header. Your service will have a key with which it can verify the Access Token signature without making any network requests or connection to your auth service. Your service can trust the authorization claims like manager: true and allow or deny the request.

It is worth noting that JWT tokens are not encrypted and anyone can read the data inside them. You can paste them at https://jwt.io and have a look for yourself. However, to verify the signature of the tokens, your services will have a static key so there isn’t a security concern here. There is though, a privacy concern if those tokens were to leak, since they will typically contain your users’ email, name and permissions. As such they should be treated like any other user-data and all your apps and services should talk over HTTPS/SSL which is the new normal.

Continuously Expiring Access Tokens

There are a few problems that arise from the passport-like nature of Access Tokens. If your address changes but you don’t update the passport then everyone who sees your passport will believe your old address is your current address.

Similarly, a claim such as manager: true could be obsolete if a manager got demoted. However, since the Access Token has already been issued, the client or whoever has it, is free to use it. There is no enforceable way to make anyone forget the token, once issued.

To overcome this challenge, all tokens have a short expiry date, determined by the developer. Typically this will be between 15 minutes to an hour. Once a token expires, it can no longer be used (just like your passport). And as you probably realize, a driving license or passport that never expires would create a lot of problems.

This expiry forces all clients to continually refetch fresh Access Tokens from your auth server, every few minutes. A client will need to have all this logic coded in so the token is refreshed before making API calls to your services. Fortunately, due to the standarized nature of JWT, all JWT-compliant server libraries will have this expiry check built-in (checking the exp field stored as an EPOCH date). That’s why it is also important to ensure your server clocks are correctly tuned (so they can check expiry) and for that matter your client clocks too (otherwise the client may thing an expired token still has some time left).

The process of fetching fresh tokens from your auth service relies on a different kind of token, called a Refresh Token. On first login, a client is actually issued two tokens: an Access Token and a Refresh Token. Subsequently, the Refresh Token is periodically (or as-needed) sent to your auth service to get a new Access Token. This all happens seamlessly and is built into the JWT client libraries out there.

A Window of Disharmony

Token refreshes happen every few minutes, which means during this brief window a demoted manager can still use his unexpired token. Your use-case must be okay with this. If you need realtime auth because your users are performing sensitive work (money, security, etc) then JWT is probably not a good idea.

The expiry window is determined by the developer and can be set to anything. If it’s too low, your clients and auth servers will be overworked and constantly issuing new Access Tokens. Furthmore, you might as well go realtime at this point with Session Auth or similar. If it’s too high, then an ex-manager can continue to access managerial features for that much longer.

Token Blacklists

A few frameworks out there reluctantly support blacklists. This is for emergencies when you want to disallow an already issued Access Token. The problem with this mechanism is obvious. Imagine you decided to put out an announcement that all passports and driving licenses now need to be checked against a database. Every bar, theatre and restricted establishment will need not contact the central authorities for every license/passport check. It’s not distributed anymore and definetly not stateless because of the central database. At this point you should evaluate your use-case and see if Session Auth is a better fit.

It is tempting to set your token expiry to a lower interval, like 1 minute but again, that’s hitting your server every minute and it still won’t solve your realtime problem. Hence, most services will settle on 15 minutes to an hour and that’s usually fine. If you really don’t care that a user who hasn’t paid up for the month can access your services for an extra hour, it’s not that big of a deal to have expiries of an hour or more.

Mixing Realtime Auth with JWT

It is possible you like the standardized nature of JWT Access Tokens and simply want to use them as glorified Session IDs. You will need to assign each Access Token an ID (just like Session ID) and store it in the database. Further you will keep the claims empty or have non-critical claims in the Token but store and check authorizations on the server side. But what have you achieved? Your standardized JWT client and server libraries will be fairly useless as all your work is happening via Session Auth. You will end up managing two systems as one. You will be updating tokens for no reason and the data will be redundant since your services will need to ignore all claims and trust your server in any case.

Other JWT Limitations

JWT tokens are base64-encoded and piggyback on HTTP headers which have an 8kb limit. So you can really only store about 5kb of data in a JWT token. This should be more than enough unless you are stuffing the token with too much metadata. For reference, the entirety of text for this post is 11kb. If you’re hitting the 5KB raw data limit, you’re likely abusing JWT token claims as a data store.

Should You Switch to JWT?

Provided you can live with a few minutes between token updates, the most compelling use-case for JWT is to allow your client to securely and directly consume your numerous distributed APIs and services with a lot of ease. In the age of microservices and everything-is-an-api, JWT can be your friend. For everything else, there is Session Auth.

Another good reason is if you have multiple apps like iOS, Android and browsers, these can benefit from standardized plug-and-play JWT client-libraries, relieving you from extra work to implement auth. There are also plenty of auth-as-a-service providers that all tend to support JWT so you can hit the ground running. Again, if you can live with the expiry delay, then JWT’s benefits are compelling.