What Is A JSON Web Token (JWT)?
JSON Web Tokens are an open industry standard (ratified in RFC 7519) that allow applications to pass authentication details around using a JSON object. For simplicity, they are shortened to "JWT" and are pronounced like the English word "jot".
There are a couple of awesome benefits to JWTs when compared to opaque tokens: they are decentralised and stateless. Opaque tokens have to be used with the issuing server so that it can look up stored information (usually a database) in order to process a request. To validate an opaque token a request has to be made to the server and only at that point will we know if it's valid.
JWTs on the other hand are "transparent" and already contain everything required for a server to process a request. They are validated by verifying their signature (more on that later) using a shared secret. Additionally, JWTs can contain an exp
field which indicates when they expire.
Use
When a client makes a successful authentication request, the server creates a JWT and hands it back. The client then uses this token when making further requests.
This is commonly achieved by using the JWT in the Authorization
header:
Authorization Bearer <JWT>
Since the token contains claim information about the user, we don't have to re-read it from a database. In an app context, this means we don't necessarily need to persist a separate user object somewhere (like shared preferences or a database), we can just store the token itself and hydrate user details directly from the token's payload.
Structure
A JWT might look odd at first, but once you understand the basic structure it's easy to follow.
💡 Tip
jwt.io is a great resource while you're implementing or debugging JWTs. Their online token debugger is fantastic and lets you inspect all the various moving parts.
A typical JWT will look like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMTM4IiwibmFtZSI6IkNoZXdpZSJ9.4-WWDH1zkdt40vBeqhy0BPrefIrqA6QPaBhKbN4lyxU
Each portion of the token (separated by a period) is a Base64Url
encoded JSON object:
{header}.{payload}.{signature}
This formatting is the biggest strength of JSON Web Tokens. Everything a client may require for authorization and user context is encoded into easily transportable ASCII text.
Header
The header
portion of the token contains information about its type and the algorithm used to generate the signature:
{
"alg": "HS256",
"typ": "JWT"
}
These two fields allow the recipient to process the rest of the token correctly.
Payload
The payload
contains a set of claims that can be held true if the signature is correct:
{
"sub": "1138",
"name": "Chewie"
}
There are different types of claims:
- Registered claims, these are predefined claims. See example claims below for common registered claims.
- Custom claims, are ones which are created by you, and subsequently aren't registered in the IANA claims registry. In order to avoid collisions it is strongly recommended that you namespace them.
The general rule of thumb is to not put anything in the header or payload that is considered secret. These are publicly readable unless the JWT is encrypted.
In an app, the payload is really the only part of the JWT we care about. It contains all the juicy information about our user.
Example claims
iat
- token issued at, in NumericDate format.exp
- token expires at, in NumericDate format.sub
- the subject of the JWT.jti
- unique token identifier, case sensitive.name
- full name.email
- preferred email address.picture
- profile picture url.
Signature
The signature
is made up of both the header
and payload
hashed together with a secret, using the algorithm specified in the header. This secret is used by the server to determine the authenticity of the JWT.
final token =
base64Url.encode(headerBytes) + '.' + base64Url.encode(payloadBytes);
final secret = 'yROnX3pWaaZs7qpapP0VXvJWccvypAYp';
var hmac = Hmac(sha256, secret.codeUnits);
var signatureDigest = hmac.convert(token.codeUnits);
var signature = base64Url.encode(signatureDigest.bytes);
Executing the above gives us the signature:
4-WWDH1zkdt40vBeqhy0BPrefIrqA6QPaBhKbN4lyxU=
💡 Hint
If at any point you get a
Base64
encoded string that ends with an=
sign, don't panic! When the input is not a length divisible by three the encoding process adds padding so that the output is a multiple of four. This is because Base64 is a six-bit encoding.In my example above, Dart's
base64Url.encode
always pads the string so that the input is a multiple of four.
A server receiving a JWT can use this signature to determine if the token was tampered with in transit. There's generally no need to verify the signature client-side, so that's one less thing to worry about.
Refreshing
Refresh tokens allow for what is known as a "sliding session". After a period of inactivity the JWT will expire, but actions performed by the user will keep them logged in. The session is kept alive by sending a refresh token to the server in order to receive a new JWT.
If both the JWT and the refresh token have expired will the user be logged out and have to re-authenticate.
JSON Web Tokens are a very popular token standard for information exchange. As with anything that makes use of cryptography, always use a battle-tested library and don't "roll-your-own".