What are JWTs & how to use them in your architecture?

What are JWTs & how to use them in your architecture?

Web Dev
April 22, 2024
Sebastiaan Wiechers
JSON Web Tokens (JWTs) are a popular method for handling authentication and authorization in microservices architectures. When you're deploying multiple services that need to communicate with each other in a secure way, you probably don't want to deal with the high latency of making an API call to a central database each time you have to transmit data between services.
JWT to the rescue!

How do JSON Web Tokens work?

A JWT consists of three parts: the header, the payload, and the signature. It might look like this:
Head over to jwt.io and paste it in. Notice the two dots, cutting the JWT in three parts:

(1) Header: The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.
In our example, the first part
is the base64 encoded header which encodes
{ "alg": "HS256", "typ": "JWT" }

(2) Payload: The second part of the token is the payload, which contains the claims. Claims are statements about an entity (typically, the user) and additional data.
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

(3) Signature: A signature created with some kind of secret that should remain secret, which can be used to verify the authenticity of the header and payload.
Using the HMAC_SHA256 algorithm (specified in the header), you can verify that a token is valid, that is, that the header and payload have been signed with the expected secret.

Benefits of using JWT

Stateless: Once a JWT is issued, it can be verified independently by any service without needing to check a central database each time.
Scalability: As services can verify tokens independently, this allows for easy horizontal scaling.
Data Embedding: JWTs can contain arbitrary data (payload), which can be used to pass important information around services.
It's important to note that the header and payload of JWTs are not encrypted, just encoded. This means that the information within a JWT is merely serialised and base64 encoded, which means that anyone who obtains your jwt can access the payload. This means it's crucial not to put sensitive data in the payload of a JWT, and only communicate over HTTPS (or other encrypted protocols, depending on your use-case).

JWTs in Supabase Auth

Supabase uses JWTs for its authentication. When a user logs in, Supabase issues a JWT, which can be used to authenticate subsequent requests. Supabase uses both access tokens and refresh tokens in its authentication process.

Access Tokens

When a user logs in, they are issued an access token. This token is then used to authenticate subsequent requests. The access token is short-lived, typically expiring in a few hours. This is a security measure to prevent long-term access if the token is compromised.

Refresh Tokens

When the access token expires, instead of asking the user to log in again, the application can use the refresh token to get a new access token. So, you might ask, what if the refresh token gets compromised?
The answer is: whereas an access token is typically a Bearer token (i.e. the holder can access a resource, no questions asked), the refresh token doesn't have to be. Whereas the access token is typically used to access the application itself, refresh tokens are typically sent to a separate auth server, in which extra measures can be implemented to verify the authenticity of the user. While access tokens are stateless, refresh tokens are not.
If a refresh token is compromised, it can be revoked by the server, preventing any further access tokens from being issued.

How it works in Supabase

When a user first logs in via the browser, the auth server issues an access-refresh token pair which gets stored as a cookie on the client-side. The access token is a signed JWT which contains the identity of the user, the veracity of which can be verified by the server. An API request bearing the access token in the header is translated into a PostgreSQL query which fetches the data, respecting RLS policies.

Security Considerations

Some vulnerabilities have been found in the past, where servers will accept JWTs when the alg field in the JWT header is set to None, instead of the name of an encryption algorithm.
Although many libraries have patched alg=None, it remains best practice to validate that the alg value corresponds to one of the signing algorithms that are actually used to sign tokens within your context, e.g. using a library like Pydantic.
If you issue a lot of JWTs, it's also a good idea to rotate the signing secret every once in a while as a counter-measure to brute-force attacks. Instead of signing JWTs with 'weak' secrets, consider using a long random hash instead. Rotating the signing secret will invalidate all existing JWTs - do you understand why?
It's best practice to store refresh tokens on the server-side only. If you're using refresh-tokens for server-client communication, and don't want to force your user to renew their session each time, refresh tokens are typically stored as a cookie in the browser. This means that if your users installed a malicious browser plug-in, attackers could issue new access tokens with stolen refresh-tokens.
Depending on the sensitivity of your application, I recommend logging metrics like IP, network latency, etc. - invalidating refresh tokens that are sent to your auth server which don't fit a common pattern, forcing your user to re-authenticate. Preferably using 2FA.

6. Conclusion

JWTs offer a scalable and stateless solution for handling authentication and authorization between different services.
They can be used securely, but you need to be aware of how they work and how they are stored to correctly implement them.
Never share your signing secret and make sure it's not 'AppleCar123'. Have a nice day!

We publish a bimonthly newsletter. Get the latest industry applications for data analytics, data engineering, data science and AI.