d-form
With the session middleware from example b-session,
we can build a secure form:
let show_form ?message request =
<html>
<body>
% begin match message with
% | None -> ()
% | Some message ->
<p>You entered: <b><%s message %>!</b></p>
% end;
<%s! Dream.form_tag ~action:"/" request %>
<input name="message" autofocus>
</form>
</body>
</html>
let () =
Dream.run
@@ Dream.logger
@@ Dream.memory_sessions
@@ Dream.router [
Dream.get "/"
(fun request ->
Dream.respond (show_form request));
Dream.post "/"
(fun request ->
match%lwt Dream.form request with
| `Ok ["message", message] ->
Dream.respond (show_form ~message request)
| _ ->
Dream.empty `Bad_Request);
]
@@ Dream.not_found
$ dune exec --root . ./form.exe
We didn't write a literal <form> tag in the template. Instead, we used
Dream.form_tag to generate
the tag. Dream.form_tag also
snuck in a hidden <input> field containing a CSRF token:
<form method="POST" action="/">
<input name="dream.csrf" type="hidden" value="j8vjZ6...">
<!-- The rest we actually wrote ourselves in the template! -->
<input name="message" autofocus>
</form>
This hidden dream.csrf field helps to
prevent CSRF attacks
attacks against the form.
Dream.form expects dream.csrf
and checks it. If there is anything wrong with the token,
Dream.form will return a value
other than `Ok _.
The form fields carried inside `Ok _ are returned in sorted order, so you
can reliably pattern-match on them.
The bad token results, like `Expired _, also carry the form fields. You can
add handling for them to recover. For example, if you receive an expired form,
you may want to resend it with some of the fields pre-filled to received
values, so that the user can try again quickly.
However, do not send back any sensitive data, because any result other than
`Ok _ might indicate an attack in progress. That said, `Expired _
and `Wrong_session _ do often occur during normal user activity. The other
constructors typically correspond to bugs or attacks, only.
Next steps: