2

I'm building a single page application using angular and clojure. Currently, a user registers their account and logs in by providing and submitting their credentials through a form in an html template. Clojure then checks to see if their password matches the encrypted copy in the database and returns a json object containing their username, first/last names, role, and some other information. The entire front end is angular, and as such, there is one main route:

(defroutes main
  (GET "/" [] (layout/master)))

The application makes various requests to service routes for other functionality:

(defroutes service
  (GET "/api/private" {params :params} (http/json-response 200 {:success true :message "it worked"}))
  (GET "/api/even-privater" {params :params} (http/json-response 200 {:success true :message "it really worked"})))

Currently, there is no security on those routes. What i would like, is to use friend/authenticate to protect those service routes, however, I cannot seem to find a workflow that works for me nor can I can find any documentation regarding using cemerick/friend for a single page application.

Ideally, a user would log in, and then be able to make requests to the secured routes. If they are not authenticated, they would simply receive 401 responses. Upon successful login, the user would receive an http 200 with some relevant user information.

I have thoroughly read through the workflows code, this stackoverflow post as well as this issue solution, but still cannot wrap my head around what I need to do. The demos found here helped me understand what friend can do, but I am finding it difficult to apply what I've learned from them.

Ideally, I would like to put the app together something like this:

(def secured-service
  (friend/authenticate
   routes/service
   {:credential-fn a-credential-function
    :unauthenticated-handler {:status 401 :body "Unauthenticated"}
    :workflows [(workflows/a-spa-workflow)]}))

(def app (middleware/app-handler
                  [routes/main routes/public secured-service routes/app]
                  :middleware []
                  :formats [:json-kw :edn]))

With authentication handled by a route that uses make-auth

(defroutes public
  (POST "/api/login" {params :params} (workflows/make-auth user-record ...)))

Does anyone know about some documentation regarding this issue that could help me? Or, better yet, any ideas on how to accomplish this?

Adeel Ansari
  • 39,541
  • 12
  • 93
  • 133
  • This seems more of server question than angular - I have done my frontend angular security like this http://jonsamwell.com/url-route-authorization-and-security-in-angular/ – Jon Jul 23 '14 at 06:37
  • That is a great setup, though I don't believe it is relevant to my question. My goal is to prevent an http request to a Clojure service route from going through if the user is not authenticated with the backend. I have security (well, show/hide is all we can do from angular) based on roles in the front end using an interceptor as well - what i need is not security in the front end, but security in the backend to protect routes that actually talk to my database and require authorization/authentication. –  Jul 23 '14 at 11:31

1 Answers1

2

I managed to solve my own problem using the friend json workflow. My code ended up looking like this:

(def api-handler
  (-> routes/api
      wrap-keyword-params
      wrap-nested-params
      wrap-params))

(def app
  (middleware/app-handler
   [routes/main routes/public routes/api routes/app]
   :middleware []
   :formats [:json-kw :edn]))

(def secure-app
  (-> app
      (friend/authenticate {:login-uri "/api/session"
                            :unauthorized-handler json-auth/unauthorized-handler
                            :workflows [(json-auth/json-login
                                         :login-uri "/api/session"
                                         :login-failure-handler json-auth/login-failed
                                         :credential-fn (partial creds/bcrypt-credential-fn session/get-user))]})
      (ring-session/wrap-session)))

Where my routes (routes/main, routes/public, routes/api, and routes/app) are:

(defroutes main
  (GET "/" [] (layout/master)))

(defroutes public
  (GET "/api/session" [] json-auth/handle-session)
  (POST "/api/session" {params :params} json-auth/handle-session)
  (DELETE "/api/session" [] json-auth/handle-session)
  (POST "/api/register" {params :params} (auth/register (:credentials params) (:name params))))

(defroutes api
  (GET "/api/private" {params :params} 
       (friend/authenticated (http/json-response 200 {:success true :message "it worked"})))
  (GET "/api/even-privater" {params :params} 
       (friend/authenticated (http/json-response 200 {:success true :message "it really worked"}))))

(defroutes app
  (route/resources "/")
  (route/not-found "Not Found"))

I now authenticate by a POST to /api/session, logout by a DELETE to /api/session, and get the current session through a GET to /api/session.