Created
September 21, 2016 02:01
-
-
Save aputs/b6e255e79ef99bf90ff80a1177044a01 to your computer and use it in GitHub Desktop.
clojurescript JWT encode/decode (SHA version only)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
nice