Dunaj enhances Clojure by providing abstractions for parsing and printing, together with implementations for commonly used formats such as Unicode charsets, Base64 or JSON. As Dunaj also provides additional abstractions and facilities for primitive collections, one can use Dunaj to create simple yet performant libraries that parse custom formats or handle low level byte collections.

JSON Web Token is a new format for representing authenticated claims. This short post will show how Dunaj can process and generate JWTs with it’s built-in functionalities.

Parsing JWT

JWT encoded in JSON Web Signature (JWS) format consists of three Base64 URL safe encoded parts:

  • JOSE header (JSON Object)

  • Claims (JSON Object)

  • Signature

As Dunaj provides built-in data formatters for both JSON and Base64, parsing JWT is dead simple:

1
2
3
4
5
6
7
8
9
10
(defn split-token :- KeywordMap
  "Parses JWT token and returns map with its parts."
  [token :- ByteColl]
  (let [parts (vec (partition-by #(i== (iDOT) %) token))
        kjson (assoc json :key-decode-fn keyword)
        pf #(parse-whole kjson (parse utf-8 (parse base64-safe %)))]
    {:signed-part (concat* (take 3 parts))
     :header (pf (first parts))
     :claims (pf (nth parts 2))
     :signature (parse base64-safe (nth parts 4 nil))}))

The above function is all that is needed to extract claims from the JSON Web Tokens.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(def my-token
  (->str "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
         "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9."
         "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"))

(def my-token-bytes (print utf-8 my-token))

(def parsed-token (split-token my-token-bytes))

parsed-token
;; {:signed-part #object[dunaj.coll.helper.Reduciblecbus 0x73002b3d "..."],
;;  :header {:alg "HS256", :typ "JWT"},
;;  :claims {:sub "1234567890", :name "John Doe", :admin true},
;;  :signature #object[dunaj.coll.helper.Reduciblecbus 0x28a24be3 "..."]
;; }

(:claims parsed-token)
;; {:sub "1234567890", :name "John Doe", :admin true}
The #object[…​ things are collection recipes. They are not actual collections but they represent a transformation over existing collection (my-token-bytes in our example). They can be reduced like normal collection with the benefit that there is no intermediate collection created, thus memory is saved and the computation is faster. Calling seq or vec will transform them into a normal collection, when such collections are needed.

Verifying signature

JWT supports several algorithms for signing claims. Following example will show how to verify HMAC signed tokens.

1
2
3
4
5
6
7
(defn hmac-sign :- ByteColl
  "Returns the result of HMAC signing data with secret."
  [alg :- String, secret :- ByteColl, data :- ByteColl]
  (let [secret-key (javax.crypto.spec.SecretKeySpec. (dha/byte-array secret) alg)
        hmac (javax.crypto.Mac/getInstance alg)]
    (.init hmac secret-key)
    (dha/adapt (.doFinal hmac (dha/byte-array data)))))

Above function will return the signature of given data. We can use it to compare with signature passed as part of JWT token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(def my-secret "secret")

(def secret-bytes (print utf-8 my-secret))

(vec (:signature parsed-token))
;; [76 -107 64 -9 -109 -85 51 -79 54 112 22 -101 -33 68
;;  76 30 -79 -61 112 71 -15 -114 -122 25 -127 -31 78 52 88 123 30 4]

(hmac-sign "HmacSHA256" secret-bytes (:signed-part parsed-token))
;; (76 -107 64 -9 -109 -85 51 -79 54 112 22 -101 -33 68
;;  76 30 -79 -61 112 71 -15 -114 -122 25 -127 -31 78 52 88 123 30 4)

;; NOTE: equality comparison prone to timing attacks
(= (seq (:signature parsed-token))
   (seq (hmac-sign "HmacSHA256" secret-bytes (:signed-part parsed-token))))
;; true

Generating JWT

The creation of custom JWT is quite simple too. All you need to do is to encode your header and claims in Base64 JSON and append the signature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(defn jwt :- ByteColl
  [claims :- {}, secret :- ByteColl]
  (let [pf #(print base64-safe (print utf-8 (print-one json %)))
        header {:typ "JWT" :alg "HS256"}
        signed-part (concat (pf header) [(iDOT)] (pf claims))
        signature (hmac-sign "HmacSHA256" secret signed-part)]
    (vec (concat signed-part [(iDOT)] (print base64-safe signature)))))

(def new-token-bytes
  (jwt {:sub "1234567890", :name "John Doe", :admin false} secret-bytes))

(def new-token (str (parse utf-8 new-token-bytes)))
;; eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
;; eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlfQ==.
;; S_mcs280KaxVTd5I3-kPeSwyrtOXZJHqfPF5RH2gQV0=
;; you can verify above wrapped token e.g. at http://jwt.io/

Notable feature of how Dunaj handles parsing/printing is that it uses Java’s NIO Buffers underneath and it does not create intermediate collections nor lazy sequences. That allows for very efficient data processing and enables the use in e.g. transducers. Following example creates core.async channel that takes bytes that represent Base64 encoded JSON objects and produces Clojure data structures on the output.

1
2
3
4
5
6
7
8
9
10
11
12
(let [xf (comp (parse base64-safe)
               (parse utf-8)
               (parse json))
      c (chan 100 xf)
      v (vec (print utf-8
               (->str "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9eyJzdWIiOi"
                      "IxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRt"
                      "aW4iOnRydWV9")))]
  (<!! (onto-chan! c v))
  (thread (pp! (<!! (reduce! conj [] c))))
  (close! c))
;; [{"typ" "JWT", "alg" "HS256"} {"sub" "1234567890", "name" "John Doe", "admin" true}]

Whole example can be found in this gist, and requires Dunaj v0.6.0 or later.

This post was published on June 2015. Back to the Blog home