diff --git a/README.md b/README.md
index e3a14ae..b05d31e 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,50 @@ Req4 = cowboy_session:drop(Req3).
Consider writing other implementations :)
+cowboy_csrf
+--------------
+
+Provide cross site request forgery (CSRF) protection.
+
+Insert `cowboy_csrf` middleware after `cowboy_session`:
+
+```erlang
+ {middlewares, [
+ ...
+ cowboy_session,
+ ...
+ cowboy_csrf, % requires cowboy_session
+ ...
+ ]}
+```
+
+Generate or retrieve CSRF from session:
+```erlang
+{CsrfToken, Req3} = case proplists:get_value(csrf_token, Session1, undefined) of
+ undefined ->
+ % did not find csrf_token in session
+ % generate a new token and add it to the session
+ NewToken = base64:encode(crypto:strong_rand_bytes(32)),
+ Session2 = Session1 ++ [{csrf_token, NewToken}],
+ Req2 = cowboy_req:set_resp_header(<<"x-csrf-token">>, NewToken, Req1),
+ Req2a = cowboy_session:set(Session2, Req2),
+ {NewToken, Req2a};
+ ExistingCsrfToken ->
+ % found an existing csrf_token in session
+ {ExistingCsrfToken, Req1}
+ end
+```
+
+Reffer to the token in ErlyDTL template's form:
+```html
+
+```
+
+Inject token into the template:
+```erlang
+Template:render([{csrf_token, CsrfToken}]),
+```
+
cowboy_common_handler
--------------
diff --git a/src/cowboy_csrf.erl b/src/cowboy_csrf.erl
new file mode 100644
index 0000000..3f9a705
--- /dev/null
+++ b/src/cowboy_csrf.erl
@@ -0,0 +1,85 @@
+%%
+%% @doc Simple CSRF prevention
+%%
+%% NB: Apply after cowboy_session middleware
+%%
+
+-module(cowboy_csrf).
+-author('rambocoder ').
+
+-behaviour(cowboy_middleware).
+-export([execute/2]).
+
+%%
+%% @doc Middleware verifying CSRF toaken in request.
+%%
+
+execute(Req0, Env0) ->
+ case cowboy_req:method(Req0) of
+ {<<"GET">>, Req1} -> {ok, Req1, Env0};
+ {<<"HEAD">>, Req1} -> {ok, Req1, Env0};
+ {<<"OPTIONS">>, Req1} -> {ok, Req1, Env0};
+ {_, Req1} ->
+ % check if CSRF token is in body, query string, header
+ case csrf_from_body(Req1) of
+ {undefined, Req2} ->
+ {ok, Req3} = cowboy_req:reply(403, [], <<"Body does not contain CSRF token">>, Req2),
+ {error, 403, Req3};
+ {error, _Reason} -> {error, 500, Req1};
+ {CSRFTokenValue, Req2} -> check_session_first(CSRFTokenValue, Req2, Env0)
+ end
+ end.
+
+check_session_first(CSRFTokenValue, Req2, Env0) ->
+ case cowboy_session:get(Req2) of
+ {undefined, Req3} ->
+ Req4 = cowboy_session:set([], Req3),
+ found_session(CSRFTokenValue, [], Req4, Env0);
+ {Session, Req3} ->
+ found_session(CSRFTokenValue, Session, Req3, Env0)
+ end.
+
+found_session(CSRFTokenValue, Session, Req3, Env0) ->
+ case proplists:get_value(csrf_token, Session, undefined) of
+ undefined ->
+ % no csrf_token in session found
+ % let's generate a new one and add it
+ NewToken = base64:encode(crypto:strong_rand_bytes(32)),
+ Session2 = Session ++ {csrf_token, NewToken},
+ cowboy_session:set(Session2, Req3),
+ Req4 = cowboy_req:set_resp_body(<<"Session was invalid. It did not contain CSRF token.">>, Req3),
+ {error, 403, Req4};
+ CSRFTokenValue ->
+ {ok, Req3, Env0};
+ SessionCSRFTokenValue ->
+ Req4 = cowboy_req:set_resp_body(<<"Invalid CSRF token">>, Req3),
+ {error, 403, Req4}
+ end.
+
+csrf_from_body(Req0) ->
+ % check in the body
+ case cowboy_req:body(Req0) of
+ {error, Reason} -> {error, Reason};
+ {ok, Buffer, Req1} ->
+ BodyQs = cowboy_http:x_www_form_urlencoded(Buffer),
+ Req2 = cowboy_req:set([{buffer, Buffer}], Req1),
+ Req3 = cowboy_req:set([{body_state, waiting}], Req2),
+ case proplists:get_value(<<"_csrf">>, BodyQs, undefined) of
+ undefined -> csrf_from_querystring(Req3);
+ TokenValue -> {TokenValue, Req3}
+ end
+ end.
+
+csrf_from_querystring(Req0) ->
+ % check in the query string
+ case cowboy_req:qs_val(<<"_csrf">>, Req0, undefined) of
+ {undefined, Req1} -> csrf_from_header(Req1);
+ {TokenValue, Req1} -> {TokenValue, Req1}
+ end.
+
+csrf_from_header(Req0) ->
+ % check in the header
+ case cowboy_req:header(<<"x-csrf-token">>, Req0, undefined) of
+ {undefined, Req1} -> {undefined, Req1};
+ {TokenValue, Req1} -> {TokenValue, Req1}
+ end.