JWT
A few years ago I read about JWT’s as the future of authentication and decided to start using them instead of cookies for API authentication. Luckily since I was using Django + Django-REST-Framework, there was a plugin; django-rest-framework-jwt that easily implemented it. It basically wrapped django users in a pyjwt method and made a DRF authenticator to tie it all together.
There was a lot of cool options, and it saved me from dealing with cookies so I chalked it up to using the latest and greatest technology and moved on.
It was’nt until I started playing with API-Star that I began to realize I was missing out on a lot of the power that JWT offers.
The Problem
To begin to understand some of the problems that JWT can help solve, lets go over how authentication normally works. The problem of authentication once you get past login, is maintaining a user’s active session. So once a user has proven they are who they say they are (username & password), the browser that user is on has to maintain its identity with the server. There are a number of ways to maintaining that session state. In server side apps, you can store the session variables in the page state, passing it back and forth between the server and browser. To make this easier, you store some portion of the session state in cookies. Cookies are passed back to the server on each request, so the server can know that a particular request is tied to a certain user.
If you have private variables that do you want maintained in state that you don’t want your user to be able to tamper with, you can store a session ID in the cookies, and have all of the state variables stored server-side in memory or in a database. The django default is database backed sessions. There are a number of options available besides database backend, such as cache backed, file based, and full on cookie based. The next problem arises when you are not using an external database or cache, how do you maintain session across a load balanced environment?
Moving the session storage outside of the application server would allow solve this load balanced problem partially. The application is now maintaining twice the database connections, requiring a beefing up of your database.
This load balanced problem gets compounded when you start dealing with micro-services, and multiplied when you start considering serverless architecture. Having every micro-service, or lambda function call to the database both makes the number of calls to the authentication database go through the roof, but also slows the execution of each of those components or functions.
Most API’s do not need complex session state, mostly they need Authorization. Once a user has proven who they are, you only need to know what they can and cannot access. If you could securely pass that around, you would’nt have to re-query the database every time.
Enter JWT
JWT set out to do exactly that. Have a token that contains a claim, that both the client and all of the other services can read, but sign in such a way that any server can verify that claim is still valid.
A JWT has 3 parts, the header, the payload, and the signature. It is period (.
) delimited so it looks like xxxxxxx.yyyyyyyyyyyyy.zzzzzzzzz
The header is mostly information about the token, what kind it is (its a JWT), and the signature algorithm used. It looks like
1 | { |
but base64 encoded for easy transport so eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
The payload is a json object, with a set of keys and values. There are a number of special keys called private claims:
iss
The issuer of the tokensub
The subject of the tokenaud
The audience of the tokenexp
The expiration time of the tokennbf
The Not Before Time of the tokeniat
The time the token was issuedjti
This is the token ID. Used for validating that one time tokens are only used once.
These private claims have special uses in JWT encoding/decoding and deal with expiry and valid use cases. The rest of the claims are just information to be passed around. These data points are where you can pass your users information and authorization data.
The signature is the secret sauce of JWTs. They take the base64 url encoded header and payload, concatenate them with a secret string, and run it through the algorithm specified in the header (in our case HMAC SHA256). This produces a signature string, that with steps that can be reproduced on any server that knows the secret string. Therefore any server can see if a token is still valid (by checking the private claims) and that is has not been tampered with (by validating the signature).
As a result, you have a tokenized data set that can be easily passed between server and client and back to any number of lambda function or microservices that does not need a connection to a database to make a secure claim of authorization. The information inside of the payload can also be safely decoded in the browser with some simple javascript :
1 | const getTokenPayload = jwt => { |
(atob()
is a builtin function that base64 decodes a string)
With this you can provide basic user data both to your client (basic username, email, name data, and basic permissions), so they can render out the proper view; and at the same time provide authorization information to your backend.
A great resource for testing and learning a bit more about JWTs is JWT.io. There is also a great scotch.io article on the subject.
Note. Because your entire authentication and authorization scheme using JWT rests on keeping secrets safe, make sure you do so properly. DO NOT commit secrets to repos. Be very careful about how you deploy them to servers. I highly recommend using somethings like Hashicorp‘s Vault to distribute the JWT secret.