5

I want to implement CSRF prevention in my Go web application. Users don't log in, but they do fill out forms and pay (via Stripe Checkout).

Posting something sets a key in a session variable (cookie) so they can later edit what they've posted, and a URL in an email allows them to come back when the cookie has expired and edit it again if need be.

From what I can see, I can use https://code.google.com/p/xsrftoken/ with the "double submitted cookie" method to implement CSRF prevention by:

  • Generate a CSRF token against an arbitrary user ID (uuid.V4() via go-uuid), like so:

    if session.Values["id"] == "" {
    session.Values["id"] = uuid.NewV4()
    }
    
    csrfToken := xsrftoken.Generate(csrfKey, session.Values["id"], "/listing/new/post")
    
  • ... and store that in the session and render it in a hidden field in the template:

    session.Values["csrfToken"] = csrfToken
    ...
    <input type="hidden" id="_csrf" value={{ .csrfToken }}>
    
  • When the user submits the form, I need to get the ID I generated, confirm that the submitted csrfToken from the form matches the one in the session, and if so, validate it with the xsrf package to confirm it hasn't expired:

    userID := session.Values["id"]
    
    if session.Values["csrfToken"] != r.PostFormValue("csrfToken") {
    http.Redirect(w, r, "/listing/new", 400)
    }
    
    if !xsrftoken.Valid(session.Values["csrfToken"], csrfKey, userID, "/listing/new/post") {
    http.Redirect(w, r, "/listing/new", 400)
    }
    

My pertinent questions are:

  • Should I generate a new token every time the form is rendered? Or is it acceptable to re-use a non-expired token for a single user session? Update: According to this answer I should only generate a new token per session (i.e. so the same user gets the same token on the same form, until the token expires)

  • Given the updated question, how do I handle the situation where a created token expires between the time the user requests the form and then submits the form? (perhaps it had 10 minutes left, and they alt+tabbed out for a while) Re-direct them back to the form (re-populated, of course!) and generate a new session id + csrf token?

  • Is there a different way to do this? Coding Horror indicates that SO generates a unique key for every HTML form sent to the client? How would I go about going down this route with the xsrf package, given that it wants a userID when generating a new key?

  • What else have I overlooked?

Community
  • 1
  • 1
elithrar
  • 23,364
  • 10
  • 85
  • 104
  • 2
    This question may help http://stackoverflow.com/questions/8655817/csrf-protection-do-we-have-to-generate-a-token-for-every-form – Intermernet Oct 02 '13 at 11:12
  • @Intermernet Great, that helps a lot. I'd done some digging here on SO but (somehow) didn't turn up that question. My current implementation (as above) is almost per-form, provided I use an appropriate session key i.e. `_csrf_listing_new` instead of just `csrfToken`. – elithrar Oct 02 '13 at 22:33

2 Answers2

6

Should I generate a new token every time the form is rendered? Or is it acceptable to re-use a non-expired token for a single user session? Update: According to this answer I should only generate a new token per session (i.e. so the same user gets the same token on the same form, until the token expires)

It is a good idea to regenerate both the token and session ID often. Given a persistent attacker and a viable entry vector, it's just a matter of time until the attacker obtains both. If, however, at least one of both identifiers regenerates before the attacker is able to crack the current one, then no problem.

Given the updated question, how do I handle the situation where a created token expires between the time the user requests the form and then submits the form? (perhaps it had 10 minutes left, and they alt+tabbed out for a while) Re-direct them back to the form (re-populated, of course!) and generate a new session id + csrf token?

You can update cookies and CSRF tokens through AJAX if you want to give your client vast time to fill out a form.

Is there a different way to do this? Coding Horror indicates that SO generates a unique key for every HTML form sent to the client? How would I go about going down this route with the xsrf package, given that it wants a userID when generating a new key?

The more tightly bound a token is to a certain action that requires authentication, the more fine-grained control you have. If you can uniquely identify each form in your application then I'd say do it.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
thwd
  • 23,956
  • 8
  • 74
  • 108
  • Thanks, I appreciate the answer. A per-form double-submitted cookie approach (i.e. save the form token in the session cookie and compare with the submitted token from the form itself) should work for my application. I'll consider the AJAX route for refreshing cookie expiry times but I was hoping to avoid added complexity at this stage. – elithrar Oct 02 '13 at 22:41
4

I've created a CSRF protection package for Go called nosurf. Here's how it handles the areas you mentioned:

  • Token is created by taking bytes from CS PRNG and encoding them using base64. It is not regenerated for every page load or every form, though there is a user-callable function for regenerating the token.
  • It is then stored in a cookie (not a session, as it's a generic middleware not intended for any specific framework only). The cookie lasts one year, but you can easily modify this duration.
  • nosurf takes care of cancelling the request and either returning 403 or calling your custom failure handler (if set). You don't have to have if CsrfCheckOk(r) { ... } or anything like that in your code.
  • Sadly, it doesn't address token expiring inbetween the page load and the form submission.

So that's it, even though I'm not sure it is the best way to handle CSRF all-around. A package for a specific framework might handle it better in some ways due to tight integration.

justinas
  • 6,287
  • 3
  • 26
  • 36
  • I am using your package and it does the job perfectly, and it REGENERATES the token every time the page gets loaded or after every request. Is there something wrong or i’m missing here. Thanks in advance – Mrkouhadi Apr 07 '23 at 03:40
  • @Mrkouhadi please see https://github.com/justinas/nosurf/issues/59#issuecomment-569436204 and follow up comments. If you're still confused about the behavior, `nosurf`'s repo is probably a better place to raise your issues than this SO question. – justinas Apr 07 '23 at 12:19