aantron/dream

Form csrf while using tyxml

Opened this issue · 5 comments

It would be useful if the form csrf would not be as strongly coupled with the templating; right now, getting it into tyxml requires a bit of work...

Opening (and documenting) the API by exposing what the input csrf element should be like would be really nice, instead of just providing the one for the included templates.

The input element is documented at Dream.csrf_tag, which generates the element:

<form method="POST" action="/">
  <input name="dream.csrf" type="hidden" value="j8vjZ6..."> <!-- !!! -->
  <input name="my.field">
</form>

The value is obtained by calling Dream.csrf_token.

Is this enough? What changes would help to make this clearer?

I created this function to do csrf with TyXML and Dream

let form_tag ?enctype ?style ~action request fields =
  let open Tyxml in
  let a =
    let attrs = [Html.a_action action; Html.a_method `Post] in
    let attrs =
      match enctype with
      | Some `Multipart_form_data ->
        Html.a_enctype "multipart/form-data" :: attrs
      | None -> attrs
    in
    match style with Some s -> Html.a_style s :: attrs | None -> attrs
  in
  Html.form ~a
    (List.append
       Html.
         [
           input
             ~a:
               [
                 a_input_type `Hidden;
                 a_name "dream.csrf";
                 a_value (Dream.csrf_token request);
               ]
             ();
         ]
       fields)

Is this enough? What changes would help to make this clearer?

Ok, I guess everything necessary is there but somehow I didn't manage to get there from the docs.

The "Forms" section starts with

[Dream.csrf_tag](https://aantron.github.io/dream/#val-csrf_tag) and [Dream.form](https://aantron.github.io/dream/#val-form) 
round-trip secure forms. [Dream.csrf_tag](https://aantron.github.io/dream/#val-csrf_tag) is used inside a form template to 
generate a hidden field with a CSRF token:

<form method="POST" action="/">
  <%s! Dream.csrf_tag request %>

My suggestion would be to present things in the order

  1. Dream has support for forms
  2. Forms are read using Dream.form
  3. Modern web forms need CSRF
  4. Dream supports this if you put the value returned from csrf_token in input name dream.csrf
  5. If you use dream templates, there is a convenient helper function csrf_tag

Of course, my perspective is of someone who has had a long break in web programming :)

If you don't want to use Dream.form_tag but you still want to use Dreams CSRF, you can use something like this to make it work with Tyxml:

let csrf_tag req =
  let open Tyxml.Html in
  let token = Dream.csrf_token req in
  input ~a:
      [ a_name "dream.csrf"
      ; a_input_type `Hidden
      ; a_value token
      ] ()
;;

Dream might also benefit from an example that shows how to do form CSRF with TyXML and Dream.