JWT and HMAC Signatures with JavaScript
One of the most widely used methods in creating trusted tokens is JSON Web Tokens (JWT). For this we have three fields: header; payload; and signature. Overall the signature is used to sign for the data, and can either be with an HMAC method (with a shared secret) or with public key encryption (such as with RSA and ECDSA). With the HMAC method we just have to define a password to sign and verify the token. In this case we will use HMAC SHA-256, HMAC SHA-384 and HMAC SHA-512.
Method |
JWT HMAC Sign: |
---|---|
Issuer (ISS): | |
Subject (sub): | |
JWT ID (jti): | ) |
Password (Use to sign) | ) |
JWT Signed Token |
Theory
We live in a 20th Century world of data, and where we just gather it and care little about its trustworthiness. But, there is a better way, and that’s to use signed tokens to protect and/or define trustworthiness. To protect, we can encrypt our data, and for the trustworthiness, we can use a digital signature. One of the most widely used methods for this is JSON Web Tokens (JWT).
With this we have:
- A header. This defines the token type (such as JWT) and the signing method that we will use.
- A payload. This defines the main payload data, and is defined in a simple JSON format. This might include the user’s ID, their email address, and so on. The fields are flexible and can be created for any purpose.
- A signature. This is either a public key signature (with RSA or ECDSA) or an HMAC signature (and which uses a given hashing method and a secret password).
The registered claim names are:
- “iss” (Issuer). This identifies the issuer of the token.
- “sub” (Subject). This defines the subject of the token.
- “aud” (Audience). This defines the general audience for the token.
- “exp” (Expiration Time). This defines the time that the token will expire.
- “nbf” (Not Before). This defines the time that the token will start.
- “iat” (Issued At). This defines the time that the token was created.
- “jti” (JWT ID). This defines the ID of the token.
Overall. the format of the token is header.payload.signature. So:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmcmVkQGhvbWUiLCJzdWIiOiJNeSBlbWFpbCIsIm5iZiI6MTY1OTEwMTA3OSwiaWF0IjoxNjU5MTAxMDc5LCJleHAiOjE2NTkxODc0NzksImp0aSI6IklEMTI0NTY3OSJ9.MXS5U2jGMyYbbs7Ek03IB_z2_1lWZCMnqIRo-TIJb6o
has three fields:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 eyJpc3MiOiJmcmVkQGhvbWUiLCJzdWIiOiJNeSBlbWFpbCIsIm5iZiI6MTY1OTEwMTA3OSwiaWF0IjoxNjU5MTAxMDc5LCJleHAiOjE2NTkxODc0NzksImp0aSI6IklEMTI0NTY3OSJ9 MXS5U2jGMyYbbs7Ek03IB_z2_1lWZCMnqIRo-TIJb6o
and which identify the header, the payload and the signature. If we do a Base64 decoding of “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9”, we get:
{"alg":"HS256","typ":"JWT"}
and for “eyJpc3MiOiJmcmVkQGhvbWUiLCJzdWIiOiJNeSBlbWFpbCIsIm5iZiI6MTY1OTEwMTA3OSwiaWF0IjoxNjU5MTAxMDc5LCJleHAiOjE2NTkxODc0NzksImp0aSI6IklEMTI0NTY3OSJ9”, we get:
{"iss":"fred@home","sub":"My email", "nbf":1659101079,"iat":1659101079,"exp":1659187479,"jti":"ID1245679"}
We can see that this token is not encrypted.
Overall, these tokens provide an excellent way to gain access to networked services, and where a user can gain a token and then pass it to the required services.
The signature methods
The methods that just provide a signature include HS256 (HMAC SHA-256), ES256 (ECDSA using P-256 and SHA-256) and RS256 ( RSASSA-PKCS1-v1_5 with the SHA-256). HS256 just has a password to provide the signature, while ES256 and RS256 require a private key to sign the token and a public key to provide it.
For HS256, there are multiple ways of defining the password such as with UTF8, Base64 or Hex. If JavaScript these can be defined with:
KJUR.jws.JWS.sign("HS256", sHeader, sPayload, {utf8: "Qwerty123"}); KJUR.jws.JWS.sign("HS256", sHeader, sPayload, {b64u: "UXdlcnR5MTIz"}); KJUR.jws.JWS.sign("HS256", sHeader, sPayload, {hex: "517765727479313233"});
Coding
Some JavaScript coding for creating a JWT is:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.5.26/jsrsasign-all-min.js"></script> <script> function gojwt(method,iss,sub,id,password) { var oHeader = { alg: method, typ: 'JWT' }; var oPayload = {}; var tNow = KJUR.jws.IntDate.get('now'); var tEnd = KJUR.jws.IntDate.get('now + 1day'); oPayload.iss = iss; oPayload.sub = sub; oPayload.nbf = tNow; oPayload.iat = tNow; oPayload.exp = tEnd; oPayload.jti = id var sHeader = JSON.stringify(oHeader); var sPayload = JSON.stringify(oPayload); var sJWT = KJUR.jws.JWS.sign(method, sHeader, sPayload, password); document.getElementById("JWT").innerHTML = "Header:\n" +sHeader; document.getElementById("JWT").innerHTML += "\nPayload:\n" +sPayload; document.getElementById("JWT").innerHTML += "\nJWT Signature:\n"+ sJWT; var isValid = KJUR.jws.JWS.verifyJWT(sJWT, password, {alg: [method],iss: [iss],sub: [sub]}); document.getElementById("JWT").innerHTML += "\nValid JWT: " + isValid; } }</script>
A sample run is:
Header: {"alg":"HS256","typ":"JWT"} Payload: {"iss":"fred@home","sub":"My email","nbf":1659107653,"iat":1659107653,"exp":1659194053,"jti":"ID124567"} JWT Signature: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmcmVkQGhvbWUiLCJzdWIiOiJNeSBlbWFpbCIsIm5iZiI6MTY1OTEwNzY1MywiaWF0IjoxNjU5MTA3NjUzLCJleHAiOjE2NTkxOTQwNTMsImp0aSI6IklEMTI0NTY3In0.SGRudayphGDRjlhYZzz9Y3cgsCvAua00j5j_M4dSb9M Valid: true