Skip to content

Instantly share code, notes, and snippets.

@ggeoffrey
Last active December 16, 2024 13:26
Show Gist options
  • Save ggeoffrey/b72ed568be4914a990790ea6b09c2c66 to your computer and use it in GitHub Desktop.
Save ggeoffrey/b72ed568be4914a990790ea6b09c2c66 to your computer and use it in GitHub Desktop.
Checking a JWT validity using JWKs in Clojure (tested with Cognito)
(ns services.jwt
(:require [buddy.sign.jwt :as jwt]
[clojure.data.codec.base64 :as b64]
[clojure.string :as str]
[cheshire.core :as json]
[buddy.core.keys :as keys] ;; You need to use [buddy/buddy-core "1.5.0-SNAPSHOT"]
[clojure.java.io :as io]))
(defn decode-b64 [str] (String. (b64/decode (.getBytes str))))
(defn parse-json [s]
;; beware, I got some tokens from AWS Cognito where the last '}' was missing in the payload
(let [clean-str (if (str/ends-with? s "}") s (str s "}"))]
(json/parse-string clean-str keyword)))
(defn decode [token]
(let [[header payload _] (clojure.string/split token #"\.")]
{:header (parse-json (decode-b64 header))
:payload (parse-json (decode-b64 payload))}))
(def token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.aUxz0yQGItWdwmzz-P9hn1Od-dm_fhjoswCluICgga8")
;; GET TOKEN CONTENT
(decode token)
;; => {:header {:alg "HS256", :typ "JWT"}, :payload {:sub "1234567890", :name "John Doe", :iat 1516239022}}
;; VERIFY TOKEN USING HS256
(jwt/unsign token "clojure" {:alg :hs256}) ;; "clojure" is the secret I put in the bottom left pane in https://jwt.io
;; VERIFY TOKEN USING RS256 (JWK)
(let [public-key (-> "keys/dev/jwks.json" io/resource slurp (json/parse-string keyword) :keys first keys/jwk->public-key)]
(try
(jwt/unsign token public-key {:alg :rs256})
(catch Throwable t
{:error true
:details (ex-data t)})))
;; => {:error true, :details {:type :validation, :cause :signature}}
;; :signature error since I got this token on https://jwt.io and tried to unsign it using my own JWK
;; I don't want to past a private token in this public gist ;) .
;; Drop a comment if you need more info.
@benwhorwood
Copy link

Thanks for the gist @ggeoffrey

I had some issues decoding Keycloak tokens with closing double quotes as well as closing curly braces missing in the base64 decoded token parts.

This was resolved by changing the base64 decoding to use java.util.Base64 instead, e.g.

https://stackoverflow.com/a/39188819

(:import java.util.Base64)

(defn decode [to-decode]
  (String. (.decode (Base64/getDecoder) to-decode)))

If you add (:import java.util.Base64) you can modify the decode function as follows and all other code in the gist should work as-is:

(defn decode-b64 [to-decode]
  (String. (.decode (Base64/getDecoder) to-decode)))

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