'ring anti-forgery token is unbound in rendered html

I need a CSRF token in order for my websockets client to connect to my server. In order to get the token to my client, I embed the token from ring.middleware.anti-forgery/*anti-forgery-token* in a <meta> tag, like so:

(defn head []
  [:head
   ...
   (let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
     [:meta {:name "csrf-token"
             :content csrf-token}])
   ...])

However, instead of getting an actual token back (i.e. a 60-character-long random base64 string) in the rendered tag, it renders as

<meta content="Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" name="csrf-token">

I also have ring-default's site-defaults wrapped around the routes as middleware, which includes ring.middleware.anti-forgery.

Here is the relevant file, handler.clj:

(ns spotify-client.handler
  (:require
    [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
    [hiccup.page :refer [include-js html5]]
    [ring.middleware.anti-forgery]
    [compojure.core :refer :all]))

(defn head []
  [:head
   ...
   (let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
     [:meta {:name "csrf-token"
             :content csrf-token}])
   ...])

(defn loading-page []
  (html5
   (head)
   [:body {:class "body-container"}
    [:div#app]
    (include-js "/js/app.js")
    [:script "spotify_client.core.init_BANG_()"]]))

(defn index-handler [_]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (loading-page)

(defroutes main-routes
  (GET "/" [] index-handler)
  ...
)

(def app (wrap-defaults main-routes site-defaults))

I'm also running all of this using lein run.

Now, according to this link, the anti-forgery token var would become bound in the context of whatever the current request was, and the ring-anti-forgery middleware is what binds it. When my project was brand new and I had just added wrap-defaults (on the initial ten or so calls to start/reload the server), the token var was bound and rendered correctly. However, after no signficant changes, the token var will not bind and it renders as the "Unbound" string. Once the ring-anti-forgery middleware stopped being called(?) and stopped binding *anti-forgery-token* to an actual value, the var was never bound again. I've tried making a new project a couple of times already and the token var stops being bound after 5-10 server reloads. What could cause the ring-anti-forgery middleware within ring-defaults to stop working even though wrap-defaults is in my code?



Solution 1:[1]

According to the docs, the *anti-forgery-token* is a dynamic variable that is only defined within the context of a wrap-anti-forgery middleware call.

Add [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] to your require form, and then add wrap-anti-forgery to your app declaration:

(ns spotify-client.handler
  (:require
    [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
    [hiccup.page :refer [include-js html5]]
    [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
    [compojure.core :refer :all]))

...

(def app
  (-> main-routes
      (wrap-anti-forgery)
      (wrap-defaults site-defaults))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Noah Bogart