Search code examples
clojuremiddlewarecompojurering

Compojure Ring Middleware How To Use Value Outside of Middleware


I have a simple route and middleware setup with compojure/swagger that is utilizing a ring middleware.

(POST "/worlds" [request]
      :body-params [name :- String]
      :header-params [token :- String]
      :middleware [wrap-api-auth]
      :summary "Creates a new world with 'name'"
      (ok (do-something-with-user)

(defn wrap-api-auth [handler]
  (fn [request]
    (let
      [token (get (:headers request) "token")
       user (db/get-user-by-token token)]
      (if user
        (handler request)     ; pass to wrapped handler
        (unauthorized {:error "unauthorized"})))))

This defines a simple route with some basic auth. The token passed in the header param is used to make a database query, which either returns a user and continues, or returns false and fails.

What I'm trying to accomplish is to pass the returned user back out so that I can use it later. I haven't had any luck, as I don't really know where I would try to add it to that I could access it later. I've tried to assoc it with the request but it doesn't appear that I can access it later. The ideal situation is I'm able to pass it to the do-something-with-user function.


Solution

  • Using assoc to add some data to the request should totally work.

    You can find an example with some code that is very close to what I have in production at https://gist.github.com/ska2342/4567b02531ff611db6a1208ebd4316e6#file-gh-validation-clj-L124

    In essence, that middleware calls

    (handler (assoc request
                    :validation {:valid true
                                 :validation valid?}))
    

    So for your case, the following should just work:

    (handler (assoc request
                    :user user))
    

    If I understood correctly, the destructuring syntax you use is from compojure-api. According to the example at https://github.com/metosin/compojure-api/wiki/Middleware I'd say that the middleware set via the :middleware key behaves just as expected and you should be able to extract the :user from the request that ultimately ends up in your route.

    So, just pass the request on to the do-something-with-user function:

    (POST "/worlds" request
          :body-params [name :- String]
          :header-params [token :- String]
          :middleware [wrap-api-auth]
          :summary "Creates a new world with 'name'"
          (ok (do-something-with-user request))
    

    It should contain the :user you assoced into it in your middleware function. Note the missing brackets around request like mentioned in the comments to this answer.