Every application we come across today implements security measures so that the user data is not misused. Security is always something that is changing and evolving. Authentication is one of the essential part of every application.
What is JWT?
JSON Web Token or JWT, is a standard for safely passing claims in space constrained environments. Simplicity, compactness and usability are key features of its architecture. JWT (JSON Web Token) is a secure way to encapsulate arbitrary data that can be sent over unsecure URL's.
JWT is generated by server with a secret key, so it can't be changed by the user. JSON Web Token is a powerful tool for confidently transmitting data between users, servers, or any other combination of services. Based on an open standard (RFC-7519), JWTs are digitally signed with an encryption algorithm, so the receiving party can trust the information contained within. In computer security this concept is known as Data Integrity. The worst thing is that JWT tokens can't be revoked.
JWTs are just some JSON data that you can verify came from someone you know. JWTs give people an easy way to pass data between each other, while at the same time verifying who created the data in the first place.
How does a JSON Web Tokens look like?
Every JWT is generated with the same structure. There are three parts, separated by a period. Each section (except signature, in some cases) is comprised of
base64url-encoded JSON containing specific information for that token.
The first section is known as the header. This is where a meta data pieces of information are contained:
alg: the algorithm used to sign the token (e.g.
HS256for HMAC SHA-256, or
RS256for RSA SHA-256).
RS256is recommended because it uses asymmetric (public/private) keys instead of relying on a shared private key;
typ: the type of token. This parameter is completely optional, however if preset, it's recommended that the value be "JWT" (always capitalized);
x5t: an optional certificate thumbprint containing a
base64url-encoded SHA-1 of the
X.509certificate corresponding to the encryption key used;
jku: the JSON Web Key (JWK) url.
kid: an optional parameter indicating which encryption key was used. This can be used as a signal to recipients that a key was changed.
This isn't a comprehensive list of all the parameters you can find in this section, but is instead a highlight of some more common parameters you'll likely encounter. You may have noticed the overall theme of this section is encryption — which is why you may also see this section referred to as the JSON Web Encryption (JWE) header.
The second section is the payload (also known as claims) and the core of any JWT. Claims are the JSON data inside the JWT. This is a place where the JWT issuer can store custom information for the receiving party. It's the data you care about, and want to pass along securely to someone else. There are Custom and Registered types of claims that can be used in the payload:
Custom Claims on the other hand do not have to be collision resistant, and can be named anything as long as the issuing and receiving party both agree on the use of the claim. It also cannot conflict with a registered claim for obvious reasons.
Registered Claims are universally defined claims in the IANA JSON Web Token Registry which are reserved for specific purposes. Some common Registered Claims are:
iss: token issuer;
sub: token's subject;
aud: token audience (who the token is intended for);
exp: token expiration in
iat: time the token was issued (issued at);
jti: unique identifier for a token;
nbf: time before which the token should not be accepted (not before).
The final section is the signature. This is what makes a JWT secure and ensures the integrity of your JWT during transport. The signature is simply a hash of all the content that was generated with the JWT. That means if any part of the JWT changes, the signature will be invalidated — rendering the JWT malformed. A JWT is signed with a JSON Web Algorithm (JWA).
base64UrlEncode(header) + "." + base64UrlEncode(payload),
There are two main ways to sign JWTs cryptographically: using symmetric or asymmetric keys. Both types are commonly used, and provide the same guarantees of authenticity.
What problem does it solve?
JWTs can neatly encompass:
- identifying information about a user;
- what a user can access;
- an expiration date;
- a signature for content validation;
- any other serializable information.
Keep in mind that anyone can decode the information contained in a JWT without knowing the private keys. For this reason, you should never put any sensitive information in a JWT. The purpose of a JWT isn't to encrypt data so it can't be read during transport, instead it allows the receiving party to trust that the data received was unaltered during transport. With that out of the way, we can dive into what makes up a JWT.
When a user sends a request with required credentials, the application checks if these credentials are valid. On validation, the application will create a token using a payload and a secret key. It will then send the token back to the user to store and send it with each request. When user sends request with this token, application verifies validity with same secret key. If the token is valid, the request is served, else the application will send an appropriate error message.
The most common use case for JWTs is authentication. There are tons of web security libraries which use JWTs as session tokens, API tokens, etc.
As JWT is self-contained, it contains all information about the user:
- User permissions
- User personal information
Stateful vs. Stateless
In the old days of the Web, authentication was a pure stateful affair. With a centralized overlord entity being responsible for tokens, the world was fairly simple:
- tokens are issued and stored in a single service for future checking and revocation;
- clients and resource servers know a single point of truth for token verification and information gathering.
This worked rather well in a world of integrated systems, when servers rendered frontends and dependencies existed on e.g. package-level and not between independently deployed applications. In a world where applications are composed by a flock of autonomous microservices however, this stateful authentication approach comes with a couple of serious drawbacks:
- basically no service can operate without having a synchronous dependency towards the central token store;
- every HTTP request requires a lookup to the data store;
- storage requirements grow as more users have active sessions;
- application could not be spreed (or it becomes very difficult) to multiple servers;
- the token overlord becomes an infrastructural bottleneck and single point of failure.
Stateless authentication describes a system/process that enables its components to decentrally verify and introspect tokens. This ability to delegate token verification allows you to (partly) get rid of the direct coupling to a central token overlord and in that way enables state transfer for authentication. Having worked in stateless authentication environments for several years, the benefits are clearly:
- less latency through local, decentralized token verification;
- custom authorization fallbacks due to local token interpretation;
- able to absolve from the need to keep track of issued tokens, and for that reason removes state (and hence reduces storage) dependencies from your system;
- increased resilience by removed network overhead.
However, stateless connections naturally also have their disadvantages:
- token size could be larger than a session ID. It could affect network performance;
- the data stored in the token is readable by the client;
- the server side needs code to generate, validate, and read tokens;
- anyone who gets a copy of the signing key can create tokens. You might not know when this happens;
- there was (is?) a bug in some libraries that accepted any JWT signed with the "none" algorithm;
- in order to revoke a JWT before it expires you need to use a revocation list. This gets you back to the server side storage issues you were trying to avoid.
A JWT is usually attached to a HTTP request via the HTTP
Authorization header as a Bearer token.
The JWT will have to be sent with every request to the backend, which is a tradeoff to consider. The great benefit of this approach is that this provides a stateless form of authentication since the server doesn't have to remember the user's information in session storage, significantly reducing the amount of work required to manage that state on the backend.
A drawback is that since JWTs are stateless, you cannot invalidate them without storing session state. JWTs will automatically be invalidated after their expiration date, but depending on how long the expiration was set for, a user could retain access to a service after being removed.
Advantages and Disadvantages
- The server side storage issues are gone. Same token can be used among different domains or different platforms.
- There is no need for database calls every time to verify the user. A single secret key will decode tokens provided by any user.
- The client side code is easy.
- Token automatically expires after the expiration time.
- The JWT size could be larger than a session ID. It could affect network performance.
- The data stored in the JWT is readable by the client.
- The server side needs code to generate, validate, and read JWTs.
- Anyone who gets a copy of the signing key can create JWTs. You might not know when this happens.
- There was (is?) a bug in some libraries that accepted any JWT signed with the "none" algorithm.
- In order to revoke a JWT before it expires you need to use a revocation list. This gets you back to the server side storage issues you were trying to avoid.
Where to store JWT?
localStorage for any sensitive data on the front-end. This is a very bad idea and will open you up to an extremely wide array of attacks that could absolutely cripple your users.
Identifying tokens for authentication, session, and advertising tracking should be stored in cookies. Those cookies have to be properly configured to prevent XSS and CSRF.
JSON Web Encryption (JWE)
While JSON Web Signature (JWS) provides a means to validate data, JSON Web Encryption (JWE) provides a way to keep data opaque to third parties. Opaque in this case means unreadable. Encrypted tokens cannot be inspected by third parties. It is important to note that just as in JWS, JWE essentially provides two schemes: a shared secret scheme, and a public/private-key scheme.
The shared secret scheme works by having all parties know a shared secret. Each party that holds the shared secret can both encrypt and decrypt information. This is analogous to the case of a shared secret in JWS.
The public/private-key scheme, however, works differently. While in JWS the party holding the private key can sign and verify tokens, and the parties holding the public key can only verify those tokens, in JWE the party holding the private key is the only party that can decrypt the token. In other words, public-key holders can encrypt data, but only the party holding the private-key can decrypt (and encrypt) that data.
JSON Web Keys (JWK) / JSON Web Keys Set (JWKS)
The JSON Web Key (JWK) is a JSON object that contains a well-known public key which can be used to validate the signature of a signed JWT.
If the issuer of your JWT used an asymmetric key to sign the JWT, it will likely host a file called a JSON Web Key Set (JWKS). The JWKS is a JSON object that contains the property keys, which in turn holds an array of JWK objects.
The service may only use one JWK for validating web tokens, however the JWKS may contain multiple keys if the service rotates signing certificates. The endpoint for retrieving a JWK(S) can vary and should be documented for your issuer.
Any time your application validates a JWT, it will attempt to retrieve the JWK(S) from the issuer in order to ensure the JWT signature matches the content.