After reading the previous article, we know that JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.
When should we use JWT?
- Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Single Sign On is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.
- Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed – for example, using public/private key pairs – you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with.
Are JWTs Secure Enough?
No matter how much we know about JWTs, a good question arises. If I get a JWT and I can decode the payload, how is that secure? I could just grab the token out of the header, decode and change the user information in the payload, and then send it back with the same correctly encoded secret, couldn’t I?
Well, JWTs aren’t concerned with encryption. They care about validation. That is to say, they can always find out whether the contents of a particular token have been manipulated. This means that user manipulation of the JWT is futile because the server will know and disregard the token. When issuing a token to the client, the server adds a signature based on the payload. Later, it verifies the payload and its matching signature.
The real question is, what is its motivation for not being concerned with encrypted contents?
- This keeps things simple. A simple implementation makes adoption easier, but it also lets each layer do what it does best (lets HTTPS handle encryption).
- JWT isn't meant to store sensitive data. Once the server receives the JWT and validates it, it is free to look up the user ID in its own database for additional information about that user (like permissions, postal address, etc.) This keeps the JWT small in size and avoids inadvertent information leakage because everyone knows not to keep sensitive data in JWT.
JWTs can either be signed, encrypted, or both. If a token is signed but not encrypted, everyone can read its contents, but can’t change it without knowing the private key. Otherwise, the receiver will notice that the signature isn’t a match anymore.
Generally speaking, this is nice, but what happens if your entire JWT is stolen?
Because JWTs are used to identify the client, if one is stolen or compromised, the attacker has full access to the user’s account in the same way they would if the attacker had compromised the user’s username and password instead.
For instance, if an attacker gets hold of your JWT, they could start sending requests to the server identifying themselves as you and perform actions like making service changes, user account updates, etc. Once an attacker has your JWT, it’s game over.
However, there is one thing that makes a stolen JWT slightly better than a stolen username and password: timing. Because JWTs can be configured to automatically expire after a set amount of time – a minute, an hour, a day, etc. – attackers can only use your JWT to access the service until it expires.
In theory, that sounds great, right? One of the ways token authentication is said to make authentication more “secure” is via short-lived tokens. That’s one of the core reasons token-based authentication has really taken off in recent years: you can automatically expire tokens and mitigate the risk of relying on forever-cached “stateless” tokens.
In the security world, relying on cached data to make sensitive decisions like who can log into a service and what they can do is considered a bad thing. Because tokens are stateless and allow for some speed improvements over traditional session authentication, the only way in which they can remain somewhat “secure” is by limiting their lifespan, so they don’t cause too much harm when compromised.
The only problem here is that if an attacker was able to steal your token in the first place, they’re likely able to do it once you get a new token. The most common ways this happens is by man-in-the-middle (MITM) attacks on your connection or getting access to the client or server directly. And unfortunately, in these scenarios, even the shortest-lived JWTs won’t help you at all.
In general, token-based authentication does not provide any additional security over typical session-based authentication relying on opaque session identifiers. While there certainly is a good number of use cases for token-based authentication, knowing how the technology works and where your weak spots are is essential.
Another interesting thing to consider is that in some cases, a stolen JWT can actually be worse than a stolen username and password.
Let’s pretend, for a moment, that your username and password have been compromised. In this scenario, if the app you’re logging into is protected with multi-factor authentication, the attacker needs to bypass additional identity-proofing mechanisms in order to gain access to your account.
While guessing or brute-forcing a username and password is a very realistic scenario, being able to compromise a user’s multi-factor authentication setup can be quite difficult. Bypassing factors like app-based authorization, SMS verification, face ID or touch ID is significantly more challenging than guessing a user’s password.
Because of this, a compromised JWT can actually be a greater security risk than a compromised username and password. Imagine a scenario like the one above – where the app a user logs into is protected by multi-factor authentication. Once the user logs in and verifies themselves via multi-factor, they are assigned a JWT to prove who they are. If that JWT is stolen, the attacker no longer needs to bypass MFA directly like they would if they only had the user’s username and password – they can now directly make requests as the user without additional identity proofing. Quite a big risk.
JWT – Pros and Cons
Based on what we stated above, JWT stands as the authentication and user verification standard of today in web-based applications, but just like many other “new and cool things”, it has its own pros and cons:
- No Database Table: This implies fewer DB queries, which implies faster response time. In case you are using paid services like DynamoDB that charge per query, JWT might reduce the costs marginally. But these can be resolved using tools like Redis in case of sessions
- Simpler to use if careful: If your architecture doesn’t use client Sessions and your security basics are clear, the development time in case of JWT is faster using existing libraries.
- Used across services: You can have one authorization server that deals with Login/Registration and generates the token, all the subsequent requests will not have to go to the authorization server as the only the Auth-server will have the private key, and rest of the severs will have the public-key to verify the signature. This is really useful for corporate systems where the authorization server is in a secure environment, e.g. a user needs to be connected to the intranet to login. But once they are logged in, the public servers can verify and proceed on. A similar setup can be used for OAuth implementation. The best part is that there is no connection between the auth-server and the rest of the servers other than the pre-defined public key.
- Compromised Secret Key: The best and the worst thing about JWT is that it relies on just one Key. Consider that this Key could be leaked by a careless or rogue developer/administrator, and then the whole system is compromised! The attacker (who has access to the Key) can easily access all user data if he has the user-id which can easily be acquired. The only way to recover from this point is to generate a new Key (Key-pair) that will be used across systems from here on. This would mean that all the existing client tokens are invalidated, and every user would have to login again. Imagine if one day all Facebook users are suddenly logged out.
- Cannot manage client from the server: Let’s say that we want all users to logout by cleaning up the cookies, but we cannot ask them to do so every time. We also need to consider the case that if a user’s mobile is stolen, they want to logout from all existing sessions (e.g. Gmail’s logout other sessions feature). Well, this is not possible with JWT.
- Cannot push Messages to clients (Identifying clients from server): In cases when we have no record about the logged-in clients on the DB end, we cannot push messages to all clients. In the case of JWT, this is not possible because identifying each client per user is not possible.
- Crypto algorithms can be deprecated: JWT completely relies on the signing algorithm. Although it isn’t as frequent anymore, in the past, many Encryption/Signing algorithms have been deprecated. So, in the case of JWT, if such a thing happens, again, every user on the platform will have to login again. Yet again, one will have to wait till all the JWT libraries are updated with the latest crypto algorithm.
- Data Overhead: The size of the JWT is more than that of a normal Session token. The more data you add to the JWT, the longer it gets linearly. Remember, each request needs the token in it for request verification. So, say a 1 KB JWT implies each request will have 1KB overhead upload, which is really bad when there is a low-speed internet connection.
- Complicated to understand: JWT uses cryptographic Signature algorithms to verify the data and get the user-id from the token. Understanding the signing algorithm requires knowing the basics of cryptography. So, in cases where the developer does not possess this knowledge, they might introduce security loopholes in the system.
Considering everything mentioned above, the real question is not whether you would use JWT, but when and how you should use it. As we said, for authorization and information exchange JWT is a solid standard, and it can be very reliable with additional security methods.
So, it is not a big surprise that JWT presently stands as a very popular standard you can use to trust requests by using signatures and exchange information between parties. Make sure you know when it’s best used when it’s best to use something else, and how to prevent the most basic security issues.