Sessions and CSRF protection

78 views
Skip to first unread message

Daniel Fitzpatrick

unread,
Jul 11, 2018, 4:49:53 PM7/11/18
to Luminus
I've got a couple of questions which hopefully have easy answers. 

1.  How do I handle sessions in a luminus SPA?
2.  How do I do CSRF in a luminus ajax call?

For #1 I am currently storing login info client-side, so a page refresh will log a user out of the app.  For all of my ajax calls I'm currently not using wrap-csrf, primarily because I'm not sure where to put the token in the request eg

;; AJAX request

(require '[ajax.core :refer [GET POST]])
(require '
[cognitect.transit :as t])

(def r (t/reader :json))
(def priorities (atom []))
(defn fetch-priorities! []
 
(GET "/priorities"
       
{:handler #(reset! priorities (t/read r %))})) ;; <------ CSRF token goes where?



I couldn't find anything on the sessions page or the csrf section to help with these questions.

Daniel Fitzpatrick

unread,
Jul 11, 2018, 8:23:02 PM7/11/18
to Luminus
Looks like I found an answer to the second question here: http://www.luminusweb.net/docs/clojurescript.html#ajax

Dmitri

unread,
Jul 11, 2018, 8:57:21 PM7/11/18
to Luminus
There's really no difference in session handling for SPAs. You'd have the user login to the application, and store the credentials in the session. I'm guessing you're referring to tracking the user on the client side. The common approach is to put a variable on the page, and use it to initialize the client session. You can see an example here:

There is a dynamic variable (declare ^:dynamic *identity*) in the layout namespace:


This variable is set to the identity of the user via middleware here:


the variable is then injected in the template:


and finally it's read in cljs:


Hope that helps, and CSRF plumbing is setup by default, so you shouldn't have to do anything extra there. There's a <project-name>.ajax namespace that's generated for ClojureScript, and it adds an interceptor that injects the token in Ajax requests. The server side is already setup to use CSRF by default as well.

Daniel Fitzpatrick

unread,
Jul 12, 2018, 12:18:28 AM7/12/18
to Luminus
Thanks for responding. I should have included some code. Sorry, please be patient.

Currently my application makes an ajax POST request for login and never asks the server to re-render the template. So I don't think I can inject session data into the template that way. I'm also a bit confused about cookies: aren't they necessary? But your example didn't use any.

I'll see if I can pare down some app code tomorrow morning.

Dmitri

unread,
Jul 12, 2018, 9:07:16 AM7/12/18
to Luminus
The token will be generated when the initial request to load the page happens, and it's independent of authentication. The session does create a cookie implicitly and that's how the server tracks clients. If you look in the network tab in the browser you should see a JSESSIONID cookie in the requests.

Daniel Fitzpatrick

unread,
Jul 12, 2018, 10:18:50 AM7/12/18
to Luminus
Alright, good to know.  I still don't quite get it.  This is what I currently have:

This is the login endpoint.  It just returns a string but in the future could return other user info


The login button:


At this point I could roll my own solution, but I'm going to spend some time trying to understand what you've written.
Auto Generated Inline Image 1
Auto Generated Inline Image 2

Daniel Fitzpatrick

unread,
Jul 12, 2018, 11:49:03 AM7/12/18
to Luminus
I'm playing around and this is starting to make sense.  I'm still a bit confused - do I associate login info with JSESSIONID? ie

(binding [*identity* (get-in request [:cookies "JSESSIONID" :value])] ... )

(get-in request [:session :identity])

is nill in all my request maps.


If I understand this correctly, the token is generated at page load (as you said), and the token will remain active for 30 minutes by default.  So if I maintain a hash-map of JSESSIONID -> login user (and maybe debounce a function for 30 minutes which clears the hash-map of that entry), I should be able to use that hash-map to login on page load, or logout after the session has expired.  Is that correct?

On Thursday, July 12, 2018 at 8:07:16 AM UTC-5, Dmitri wrote:
Message has been deleted

Daniel Fitzpatrick

unread,
Jul 12, 2018, 12:09:06 PM7/12/18
to Luminus
Played around some more and it didn't work quite like I expected.  I set the timeout to 30 seconds and refreshed the page after the timeout had expired, expecting to be issued a new token.  However, I was issued the same token.  My hash-map idea won't work.

Dmitri

unread,
Jul 12, 2018, 1:01:30 PM7/12/18
to Luminus
I think you're confusing CSRF with authentication here. These are two separate topics. The CSRF mechanism allows the server to validate that the request is coming from the page that it served, and not being injected via man-in-the-middle attack. This is a completely separate topic from user authentication. So your login workflow shouldn't rely on CSRF, and you don't need to worry about the JSESSIONID cookie for it. You should instead track whether there's a user key in the session or not to decide whether a user is logged in.

Daniel Fitzpatrick

unread,
Jul 12, 2018, 4:43:58 PM7/12/18
to Luminus
Alright.  Is this a good start?


I'm not sure what to do with the additional session data on the client-side once it's returned.
Auto Generated Inline Image 1

Dmitri

unread,
Jul 12, 2018, 8:48:07 PM7/12/18
to Luminus

Basically, you'd want to set the update session on the response, and Ring session middleware will take care of tracking the session state for you. You want o explicitly return the login info for the client. So you'd do something like this:

(POST "/login" [user pass :as {sess :session}]
 
(if-let [user-map (login user pass)]
   
(assoc (response/ok {:user (:id user-map)}) :session (assoc sess :user user-map))
   
(response/unauthorized {:error "login failed"})))

The session here is separate from the actual state of the user login. It's just a map that exists for the duration of the session, and it's tracked by Ring session middlware. The response you provide to the client will have the user information added in it explicitly as the payload.
Message has been deleted

Daniel Fitzpatrick

unread,
Jul 13, 2018, 11:28:39 AM7/13/18
to Luminus
This code seems to work.  I'm guessing my render-json fn was returning a map missing some required information.  Thanks, man.  Sorry I made it such a headache.

Daniel Fitzpatrick

unread,
Jul 13, 2018, 11:31:23 AM7/13/18
to Luminus
This may also explain why my earlier attempts at CSRF were abject failures, lol.

Dmitri

unread,
Jul 13, 2018, 11:40:31 AM7/13/18
to Luminus
Glad to hear things are working. :-)
Reply all
Reply to author
Forward
0 new messages