Decode and verify JSON Web Tokens in Emacs.
Verification is supported for RSA and HMAC signatures, with SHA256, SHA384 and SHA512 hashes.
Signing is supported for HMAC only.
No user-specific configuration. Simply install from this repo.
E.g. using straight
(use-package jwt
:straight (jwt :fetcher git :url "https://github.com/joshbax189/jwt-el"))
- decode JWT strings and view contents in a buffer
- display docs for defined claims using eldoc
- access claims within JWTs from elisp code
- check token expiry time
- verify HMAC signatures
- verify RSA signatures
- create HMAC signed JWTs
- verify tokens signed with ECC
Run M-x jwt-decode
then paste the token string into the minibuffer prompt.
This will output the decoded token contents to a buffer.
Alternatively, M-x jwt-decode-at-point
will do the same but working on a token in a buffer.
Displaying JWT contents in a buffer
When viewing a decoded JWT, you can verify it using C-c C-c
(or M-x jwt-verify-current-token
), then paste the
key into the minibuffer.
Note that because it is hard to copy raw bytes, the key is assumed to be either an RSA key or a base-64 encoded byte string.
Here are some examples of using jwt.el
in your own code.
For example, to get the “sub” claim:
(let* ((token-string "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")
(decoded-token (jwt-to-token-json token-string)))
(map-elt (jwt-token-json-payload-parsed decoded-token) "sub"))
Note that the output of jwt-token-json
has JSON strings in its slots. You need to use either
jwt-token-json-payload-parsed
or jwt-token-json-header-parsed
to get the JSON as a Lisp object.
The map
library is very helpful for dealing with parsed JSON.
To verify HMAC signatures you need the private key used to sign the token.
Note that the key is read as a list of bytes. So if your key is base64 encoded, you should first decode it:
(let* ((key-string "IUILGn96IQgxBoPgvH0IJ7J9+KhqCGzvwgBZI88qwsw=")
(byte-string (base64-decode-string key-string)))
(print (string-to-list byte-string)))
(let* ((key-string "IUILGn96IQgxBoPgvH0IJ7J9+KhqCGzvwgBZI88qwsw=")
(key (base64-decode-string key-string) "a.b.c")
(token "eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Adw8gL7v0IwVQsq3B8n6v_0AxdUBJoLmPTiFaP9LYCA"))
(jwt-verify-signature token key))
RSA signatures only need the public key of the signer for verification.
You can use either of these key formats,
e.g. “RSA PUBLIC KEY”
-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB -----END RSA PUBLIC KEY-----
or “PUBLIC KEY”
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo 4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u +qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ 0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc mwIDAQAB -----END PUBLIC KEY-----
Within elisp, verification is like so:
(let ((test-key "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----")
(encoded-jwt "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"))
(jwt-verify-signature
encoded-jwt
test-key))
Here we sign with just a randomly generated key:
(let* ((key (jwt--random-bytes 64))
(payload '((sub . "1234567890")
(name . "John Doe")
(iat . 1516239022))))
(jwt-create payload "hs256" key))
You can set the “iat” claim to reflect current time, like so:
(let* ((key (jwt--random-bytes 64))
(payload '((sub . "1234567890")
(name . "John Doe")))
(token (jwt-create payload "hs256" key nil 't)))
(map-elt (jwt-token-json-payload-parsed (jwt-to-token-json token)) "iat"))
- add jwt-decode-region
- add IANA defined claims to eldoc
- fix token string predicate
- decode tokens
- verify with HMAC and RSA signatures
- print help for defined claims