Skip to content

Instantly share code, notes, and snippets.

@Deraen
Last active October 1, 2019 08:40
Embed
What would you like to do?
Compojure-api and Buddy
  • (:identity req) is auth backend independent way to access user data
  • login and logout implementation depends on auth backend
  • :current-user doesn't imply that authentication is required, route should also have :auth-rules if authentication is required
(ns backend.access
(:require [buddy.auth :refer [authenticated?]]))
(defn authenticated [req]
(authenticated? req))
(defn admin [req]
(and (authenticated? req)
(#{:admin} (:role (:identity req)))))
(ns backend.restructure
(:require [compojure.api.meta :refer [restructure-param]]
[backend.access :as access]))
(defn access-error [req val]
(unauthorized val))
(defn wrap-rule [handler rule]
(-> handler
(wrap-access-rules {:rules [{:pattern #".*"
:handler rule}]
:on-error access-error})))
(defmethod restructure-param :auth-rules
[_ rule acc]
(update-in acc [:middlewares] conj [wrap-rule rule]))
(defmethod restructure-param :current-user
[_ binding acc]
(update-in acc [:letks] into [binding `(:identity ~'+compojure-api-request+)]))
(ns backend.handler
(:require [ring.util.http-response :refer [ok unauthorized forbidden]]
[ring.middleware.session :refer [wrap-session]]
[compojure.api.sweet :refer :all]
[buddy.auth :refer [authenticated? throw-unauthorized]]
[buddy.auth.backends.session :refer [session-backend]]
[buddy.auth.accessrules :refer [wrap-access-rules]]
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
[backend.access :as access]
backend.restructure))
; FIXME: From config
(def cookie-name "backend-session")
(def auth-backend
; By default responds with 401 or 403 if unauthorized
(session-backend))
(defn wrap-app-session [handler]
(-> handler
(wrap-authorization auth-backend)
(wrap-authentication auth-backend)
(wrap-session {:cookie-name cookie-name})))
(defapi app'
{:swagger {:ui "/api-docs"
:spec "/swagger.json"}}
(POST "/login" []
(assoc-in (ok) [:session :identity] {:_id 1, :username "juho", :role :admin}))
(POST "/logout" []
(assoc-in (ok) [:session :identity] nil))
(GET "/foo" []
:auth-rules access/authenticated
; :auth-rules {:or [access/authenticated access/other-predicate]}
; :auth-rules {:and [access/authenticated access/other-predicate]}
:current-user user
(ok user))
(def app
(-> app'
wrap-app-session))
(ns backend.handler
(:require [ring.util.http-response :refer [ok unauthorized forbidden]]
[compojure.api.sweet :refer :all]
[buddy.auth :refer [authenticated? throw-unauthorized]]
[buddy.sign.jwt :as jwt]
[buddy.auth.backends :as backends]
[buddy.auth.accessrules :refer [wrap-access-rules]]
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
[backend.access :as access]
backend.restructure))
(def secret "FIXME")
(def auth-backend
(backends/jws {:secret secret
:authfn (fn [token-data]
;; TODO: This option can be used (I think) to
;; validate e.g. token expiration date (return nil if not valid)
;; or to retrieve the real data from database based on value of token
token-data)}))
(defn wrap-app [handler]
(-> handler
;; TODO: If token only contains token-id which refers to database,
;; middleware could be added here to load the data from DB?
(wrap-authorization auth-backend)
(wrap-authentication auth-backend)))
(defapi app
{:swagger {:ui "/api-docs"
:spec "/swagger.json"
;; Adds field to Swagger-UI to provide the value for Authorization header
:data {:securityDefinitions {:api_key {:type "apiKey" :name "Authorization" :in "header"}}}}}
(POST "/login" []
;; Then token is provided, this value is added to request :identity if the token is valid
;; It is good idea to add some data here for token validation, like expiration date
;; or token id that is used to validate token against database.
(ok {:token (jwt/sign {:_id 1 :username "juho"} secret)}))
(GET "/foo" []
:auth-rules access/authenticated
; :auth-rules {:or [access/authenticated access/other-predicate]}
; :auth-rules {:and [access/authenticated access/other-predicate]}
:current-user user
(ok user))
(def app
(-> app'
wrap-app))
@ticean
Copy link

ticean commented Oct 1, 2016

@Deraen thank you so much for publishing this gist! Really helped me pull the pieces together on the Buddy addition to my Compojure API project.

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