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.
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 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.
A JWT might look odd at first, but once you understand the basic structure it's easy to follow.
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:
Each portion of the token (separated by a period) is a
Base64Url encoded JSON object:
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 portion of the token contains information about its type and the algorithm used to generate the signature:
These two fields allow the recipient to process the rest of the token correctly.
payload contains a set of claims that can be held true if the signature is correct:
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.
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.
picture- profile picture url.
signature is made up of both the
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:
If at any point you get a
Base64encoded 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.encodealways 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.
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".