-
Notifications
You must be signed in to change notification settings - Fork 672
JWT Primer
JWT is a standard used for authorisation and/or verifiable data storage/transfer between a client and server. In practice this usually means between a user of a site and the site itself.
JWT is often used for 'sessionless' user activity, authorisation, and management.
While JWTs have common (or required) 'claims' - or data key:value pairs - they are flexible to include whatever payload claims the developer decides to implement.
JWTs should be created by the server either at the beginning of a user's interaction with a site/application, or once the user authenticates with the application. They are then sent to the user as part of a server response - typically either in a Set-Cookie HTTP header or in the data portion of a HTTP response to an API call.
The client's browser or some client-side code then processes the JWT to store it as a cookie, in Local Storage, Session Storage or a JavaScript variable.
From this point when the client sends relevant requests to the application the JWT will be sent as well. Typically it will be sent in the HTTP Request header, either as a cookie (commonly named auth, JWT, or some variation), or in a standardised authentication header, such as a Bearer token; although it could be sent in a URL parameter, within POST data, or any other way the developer decides to play with it.
A JWT is made up of three parts:
- Head
- Body
- Signature
The first two parts are JSON objects that contain a list of 'claims' (key:value pairs), while the signature is hexadecimal data created by the signing process. Each of these the sections is base64 encoded for safe transfer over the HTTP protocol. They have the base64 padding - the trailing equals sign(s) - removed, and each section concatenated with a dot (full-stop/period).
So the following:
Header: {"alg":"HS256","typ":"JWT"}
Payload: {"login":"ticarpi"}
Are encoded as:
Header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload: eyJsb2dpbiI6InRpY2FycGkifQ==
These are stripped and joined as:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ
The Signature is generated by signing the preceding data with a secret or key: \x6e\xc4\xb0\xaa\x3d\x9c\xda\xe2\x3d\x9f\xbf\x9a\x8e\x68\xb7\x8b\x15\x46\x84\xf5\x22\x63\xb8\xce\xf5\x25\x27\xf5\xd9\xb5\xe4\xfa
The Signature is then base64 encoded:
bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po=
...and finally stripped and concatenated as the final token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po
As the Header and Payload sections are encoded with the reversible base64 method, the data can be read by anyone who is able to see the token. For this reason the data sent MUST NOT contain any sensitive data (such as passwords).
To read the contents you just need to pass each section into a base64 decode function, a few examples are:
Linux base64 tool (with the -d flag for decoding):
$ echo eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 | base64 -d
{"typ":"JWT","alg":"HS256"}
Browser JavaScript console:
>> atob("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")
"{"typ":"JWT","alg":"HS256"}"
Powershell:
PS C:\> [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"))
{"typ":"JWT","alg":"HS256"}
Hacky Windows CMD method:
C:\>echo eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 > file.txt && certutil -decode -f file.txt outfile.txt && type outfile.txt
Input Length = 40
Output Length = 27
CertUtil: -decode command completed successfully.
{"typ":"JWT","alg":"HS256"}
Due to needing to be sent over HTTP the token is encoded with the "URL-safe" version of base64 (which replaces the standard additional non-Alphanumeric characters "+" with "-" and any "/" with "_"). Some implementations may choose to use standard base64 encoding. I have also seen standard URL encoding used on non-URL-safe base64 tokens (which replaces the "+" with "%2B" and the "/" with "%2F"), so you always need to make sure you follow the existing scheme when testing!
For example:
- URL-Safe B64:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.ZHe_3OPuWfBu-x759-tuA2XUsY-tXodAiKSybwJozrA
- Standard B64:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.ZHe/3OPuWfBu+x759+tuA2XUsY+tXodAiKSybwJozrA
- URL-Encoded:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.ZHe%2F3OPuWfBu%3Bx759%3BtuA2XUsY%3BtXodAiKSybwJozrA
In practice it is almost always only the signature that contains any of the standard/URL-safe relevant extra characters, as these only appear in base64 when non-ASCII hex values are observed.