Implement CSRF protection

This adds a middleware which, when activated, will deny any form submission which does not include a valid `authenticity_token` parameter or `http-x-csrf-token` header with the request.

The header and parameter names are identical to the ones supported by Ruby's rack-protection gem for interoperability purposes.
This commit is contained in:
Mike Perham 2016-06-28 15:50:43 -07:00
parent 7e49237468
commit e407d0195c
2 changed files with 117 additions and 0 deletions

View file

@ -0,0 +1,73 @@
require "../spec_helper"
describe "Kemal::Middleware::CSRF" do
it "sends GETs to next handler" do
handler = Kemal::Middleware::CSRF.new
request = HTTP::Request.new("GET", "/")
io_with_context = create_request_and_return_io(handler, request)
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
client_response.status_code.should eq 404
end
it "blocks POSTs without the token" do
handler = Kemal::Middleware::CSRF.new
request = HTTP::Request.new("POST", "/")
io_with_context = create_request_and_return_io(handler, request)
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
client_response.status_code.should eq 403
end
it "allows POSTs with the correct token in FORM submit" do
handler = Kemal::Middleware::CSRF.new
request = HTTP::Request.new("POST", "/",
body: "authenticity_token=cemal&hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 403
current_token = context.session["csrf"]
handler = Kemal::Middleware::CSRF.new
request = HTTP::Request.new("POST", "/",
body: "authenticity_token=#{current_token}&hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded",
"Set-Cookie" => client_response.headers["Set-Cookie"]})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 404
end
it "allows POSTs with the correct token in HTTP header" do
handler = Kemal::Middleware::CSRF.new
request = HTTP::Request.new("POST", "/",
body: "hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 403
current_token = context.session["csrf"]
handler = Kemal::Middleware::CSRF.new
request = HTTP::Request.new("POST", "/",
body: "hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded",
"Set-Cookie" => client_response.headers["Set-Cookie"],
"http-x-csrf-token" => current_token })
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 404
end
end
def process_request(handler, request)
io = MemoryIO.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
handler.call(context)
response.close
io.rewind
{io, context}
end