Skip to content

Instantly share code, notes, and snippets.

@aputs
Created September 21, 2016 02:01
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save aputs/b6e255e79ef99bf90ff80a1177044a01 to your computer and use it in GitHub Desktop.
Save aputs/b6e255e79ef99bf90ff80a1177044a01 to your computer and use it in GitHub Desktop.
clojurescript JWT encode/decode (SHA version only)
(ns cljsjs.jwt
(:require
[clojure.spec :as s]
[clojure.string :as str]
[goog.json :as json]
[goog.crypt.base64 :refer [encodeString decodeString]]))
;; https://github.com/Caligatio/jsSHA
;; goog.crypt.hmac produces different signature than nodejs version
(def jssha (js/require "jssha"))
(def signing-algorithm-map
{"HS256" "sha256"
"HS384" "sha384"
"HS512" "sha512"
"RS256" "RSA-SHA256"})
(def signing-type-map
{"HS256" "hmac"
"HS384" "hmac"
"HS512" "hmac"
"RS256" "sign"})
(defn- base64-url-escape
[b64string]
(-> b64string
(str/replace "+" "-")
(str/replace "/" "_")
(str/replace "=" "")))
(defn- base64-url-encode [string] (base64-url-escape (encodeString string)))
(defn- create-hmac
[algo key]
(condp = algo
"sha256" (jssha. "SHA-256" "TEXT")
"sha384" (jssha. "SHA-384" "TEXT")
"sha512" (jssha. "SHA-512" "TEXT")
:else (throw (js/Error. (str "unknown hmac hasher `" algo "`")))))
(defn- sign
[input key algo type]
(condp = type
"hmac"
(let [hmac (create-hmac algo type)]
(.setHMACKey hmac key "TEXT")
(.update hmac input)
(base64-url-escape (.getHMAC hmac "B64")))
:else (throw (js/Error. "algorithm not supported"))))
(defn- verify-sig
[input sig key method type]
(condp = type
"hmac" (= sig (sign input key method type))
:else (throw (js/Error. "algorithm not supported"))))
(defn ^:export decode
"decode from JWT"
[token key & [no-verify algo]]
(let [segments (s/conform (s/cat :header string? :payload string? :signature string?)
(str/split token "."))]
(if-not (map? segments)
(throw (js/Error. "invalid token"))
(let [header (json/parse (decodeString (:header segments)))
payload (json/parse (decodeString (:payload segments)))
no-verify (if (nil? no-verify) false)]
(if no-verify
payload
(let [now (.. js/Date (now))
typ (.. header -typ)
alg (.. header -alg)
nbf (.. header -nbf)
exp (.. header -exp)
signing-method (get signing-algorithm-map (or algo alg))
signing-type (get signing-type-map (or algo alg))]
(if-not (= "JWT" typ) (throw (js/Error. "not valid jwt token typ")))
(if-not (and signing-method signing-type) (throw (js/Error. "algorithm not supported")))
(if (< now (* 1000 nbf)) (throw (js/Error. "token not yet active")))
(if (> now (* 1000 nbf)) (throw (js/Error. "token already expired")))
(if-not (verify-sig (str (:header segments) "." (:payload segments))
(:signature segments)
key signing-method signing-type)
(throw (js/Error. "signature verification failed")))
payload))))))
(defn ^:export encode
"encode to JWT"
[payload key & [algo extra-headers]]
(let [algo (or algo "HS256")
extra-headers (or extra-headers {})
signing-method (get signing-algorithm-map algo)
signing-type (get signing-type-map algo)]
(if-not (map? payload)
(throw (js/Error. "payload should be in JSON format")))
(if-not (map? extra-headers)
(throw (js/Error. "extra-headers should be a map")))
(if-not (and signing-method signing-type)
(throw (js/Error. "algorithm not supported")))
(let [header (base64-url-encode (json/serialize (clj->js (apply conj extra-headers {:alg algo :typ "JWT"}))))
payload (base64-url-encode (json/serialize (clj->js payload)))
signature (sign (str header "." payload) key signing-method signing-type)]
(str header "." payload "." signature))))
@deepaksingh007
Copy link

nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment