ruk·si

JSON Web Token (JWT)

Updated at 2022-10-29 22:48

JSON Web Token (JWT) is an open standard defining a self-contained way for securely transmitting information between parties as a JSON object. The core idea is that the ones delivering the message can read it but can't modify it.

JWT is mainly used for authentication and authorization. Technically, JWTs can be used to transfer any information that must not change in transit.

You have a macthing passport that tells who you are. (authentication)

Your passport lists your age which allows you to buy alcohol. (authorization)

The main benefit of JWT is the global adoption. There are multiple JWT libraries for all the major programming languages. Many authentication servers support JWT out of the box like AWS Cognito.

JWT authentication is relatively simple, sufficiently secure and highly scalable.

JWTs are stateless. Services can verify JWTs independently without having to access the authentication server or your database. Works great with stateless services.

JWTs consists of three unencrypted parts separated by dots; {1}.{2}.{3} where:

  1. JOSE Header: JSON Object Signing and Encryption header is base64Url encoded JSON that describes e.g. the type of the token in typ (JWT) and hashing algorithm used in alg.
  2. JWS Payload: JSON Web Signatures payload is base64Url encoded JSON that contains claims. Claims are statements about the entity or additional data.
  3. JWS Signature: JSON Web Signatures signature is used to verify the sender and to ensure that the header or the payload hasn't been changed along the way.

Normal JWT is not an encryption.

JWTs are like passwords and should be treated such. JWTs should always be communicated over HTTPS or similar.

JWT contents are not encrypted; their integrity is just verified. This means you should not put secret information in the token payload. Everyone can read what is the actual payload.

Assume that anything written to the JWT payload will be read and used by users. You should handle JWT contents like any API response and be backwards compatible.

Always use asymmetric keys. Here are your top picks as of 2022, pick the top one that your libraries support:

  1. EdDSA - Edwards-curve Digital Signature Algorithm
  2. ES256 - The Elliptic Curve Digital Signature Algorithm (ECDSA) using P-256 and SHA-256
  3. RS256 - RSASSA-PKCS1-v1_5 using SHA-256

Use HS256 (HMAC using SHA-256) if you really really need a symmetric key, but this is less secure as now all services can write verified tokens like an auth server could.

On the first login, a client is usually issued two tokens: an access token and a refresh token. Subsequently, the refresh token is periodically sent to your auth service to get a new pair of tokens. But many JWT libraries handle this automatically.

  • access token provides authentication and authorization details
  • refresh token provides authentication and allows getting access tokens
  • identity token provides details about the owner (optional)

You should always validate incoming JWTs. You can only skip the validation when you receive a JWT from the authentication server over HTTPS.

Whitelist signing algorithm in alg header claim. An attacker can intentionally use a weaker algorithm like none.

Whitelist issuer claim iss. Check that the signing key used belongs to exactly that issuer. E.g. if you're using OpenID Connect, the issuer must be a URL using the HTTPS scheme. This makes it much easier to confirm the ownership of the keys. Thus, it's a good practice to always use such URLs as the issuer value.

  • token claim iss is the auth server URL that generated it

iss: https://sso.example.com

Whitelist audience claim aud. The resource server should always check the audience aud claim in the token and confront it with a whitelist. The receiver should be part of the audience.

  • access token claim aud is URL(s) of the API its intended for
  • refresh token claim aud is the auth server URL
  • identity tokens don't have aud.

aud: https://api.example.com

You should have an expire on your JWTs, the exp claim. Infinite authentication tokens are dangerous as they can be stolen and used indefinitely. You should either have extend endpoint for users manually to refresh with a valid token or have a separate long-lived refresh token.

Honor exp and nbf claims yourself. iat can be an extra check for really old or from future. Up to 30 second skew is theoretically possible, but a few seconds is more common.

JWT exp should be hours or less. 2 - 15 min is most common. Never days or above. The reason is that permissions change only on token refresh. They can't be revoked.

Because of the authorization change lag, JWTs are probably not the best for real time financial or high security cases where authorization granularity is needed. Session auth with the possibility of session clear is probably better.

Identify client subjects with claim sub. Also, jti claims are sometimes used to obliterate the chance of duplicate JWTs, as same client could generate it twice on the same second. This can be an issue if you have refresh token rotation and two different clients get the exactly same refresh token.

Rotate your signing keys. It's a good practice to always use an endpoint and dynamically download the signing keys from the authorization server (caching responses accordingly to what the server returns in the cache control headers). This allows for an easy key rotation, and any such rotation will not break your implementation.

Do not use JWT for sessions. They are not meant for that and are less secure. Use session cookies like normal.

Do not use JWT as a database. HTTP headers have an 8 kb limit. So you can store about 5 kb of data in a JWT token.

Sources