class Rack::Protection::AuthenticityToken
- use Rack::Protection::AuthenticityToken, authenticity_param: ‘your_token_param_name’
:authenticity_param
option:
To customize the authenticity parameter for form data, use the
== Example: Customize which POST parameter holds the token
Rack::Handler::WEBrick.run app
end
end
]]
EOS
</html>
</body>
</form>
<input type=“submit” />
<input type=“text” name=“foo” />
<input type=“hidden” name=“authenticity_token” value=“#{Rack::Protection::AuthenticityToken.token(env)}” />
<form action=“” method=“post”>
<p>This successfully takes you to back to this form.</p>
<h1>With Authenticity Token</h1>
</form>
<input type=“submit” />
<input type=“text” name=“foo” />
<form action=“” method=“post”>
<p>This takes you toForbidden
</p>
<h1>Without Authenticity Token</h1>
<body>
</head>
<title>rack-protection minimal example</title>
<meta charset=“UTF-8” />
<head>
<html lang=“en”>
<!DOCTYPE html>
<<~EOS
[200, {}, [
run -> (env) do
use Rack::Protection::AuthenticityToken
use Rack::Session::Cookie, secret: ‘secret’
app = Rack::Builder.app do
require ‘rack/protection’
Here isserver.rb
:
ruby server.rb
gem install ‘rack-protection’
Install the gem, then run the program:
The one without CSRF token field will get a 403 Forbidden response.
program which shows two forms. One with, and one without a CSRF token
To show what the AuthenticityToken does, this section includes a sample
== Example: Forms applicationnil
:allow_if
] a proc for custom allow/deny logic. Default value::csrf
the token in the session. Default value:
[:key
] the name of the param that should contain"authenticity_token"
the token on a request. Default value:
[:authenticity_param
] the name of the param that should contain
== Options
Compatible with the rack-csrf gem.
data.
It checks theX-CSRF-Token
header and thePOST
form
token matches the token included in the session.HEAD
,OPTIONS
,TRACE
if their given access
This middleware only accepts requests other thanGET
,
More infos - en.wikipedia.org/wiki/Cross-site_request_forgery<br>Supported browsers
- all
Prevented attack -
CSRF
#
- all
def self.random_token
def self.random_token SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false) end
def self.token(session, path: nil, method: :post)
def self.token(session, path: nil, method: :post) self.new(nil).mask_authenticity_token(session, path: path, method: method) end
def accepts?(env)
def accepts?(env) session = session(env) set_token(session) safe?(env) || valid_token?(env, env['HTTP_X_CSRF_TOKEN']) || valid_token?(env, Request.new(env).params[options[:authenticity_param]]) || ( options[:allow_if] && options[:allow_if].call(env) ) rescue false end
def compare_with_global_token(token, session)
def compare_with_global_token(token, session) secure_compare(token, global_token(session)) end
def compare_with_per_form_token(token, session, request)
def compare_with_per_form_token(token, session, request) secure_compare(token, per_form_token(session, request.path.chomp('/'), request.request_method) ) end
def compare_with_real_token(token, session)
def compare_with_real_token(token, session) secure_compare(token, real_token(session)) end
def decode_token(token)
def decode_token(token) Base64.urlsafe_decode64(token) end
def encode_token(token)
def encode_token(token) Base64.urlsafe_encode64(token) end
def global_token(session)
def global_token(session) token_hmac(session, GLOBAL_TOKEN_IDENTIFIER) end
def mask_authenticity_token(session, path: nil, method: :post)
def mask_authenticity_token(session, path: nil, method: :post) set_token(session) token = if path && method per_form_token(session, path, method) else global_token(session) end mask_token(token) end
def mask_token(token)
on each request. The masking is used to mitigate SSL attacks
Creates a masked version of the authenticity token that varies
def mask_token(token) one_time_pad = SecureRandom.random_bytes(token.length) encrypted_token = xor_byte_strings(one_time_pad, token) masked_token = one_time_pad + encrypted_token encode_token(masked_token) end
def masked_token?(token)
def masked_token?(token) token.length == TOKEN_LENGTH * 2 end
def per_form_token(session, path, method)
def per_form_token(session, path, method) token_hmac(session, "#{path}##{method.downcase}") end
def real_token(session)
def real_token(session) decode_token(session[options[:key]]) end
def set_token(session)
def set_token(session) session[options[:key]] ||= self.class.random_token end
def token_hmac(session, identifier)
def token_hmac(session, identifier) OpenSSL::HMAC.digest( OpenSSL::Digest::SHA256.new, real_token(session), identifier ) end
def unmask_token(masked_token)
def unmask_token(masked_token) # Split the token into the one-time pad and the encrypted # value and decrypt it token_length = masked_token.length / 2 one_time_pad = masked_token[0...token_length] encrypted_token = masked_token[token_length..-1] xor_byte_strings(one_time_pad, encrypted_token) end
def unmasked_token?(token)
def unmasked_token?(token) token.length == TOKEN_LENGTH end
def valid_token?(env, token)
Checks the client's masked token to see if it matches the
def valid_token?(env, token) return false if token.nil? || token.empty? session = session(env) begin token = decode_token(token) rescue ArgumentError # encoded_masked_token is invalid Base64 return false end # See if it's actually a masked token or not. We should be able # to handle any unmasked tokens that we've issued without error. if unmasked_token?(token) compare_with_real_token(token, session) elsif masked_token?(token) token = unmask_token(token) compare_with_global_token(token, session) || compare_with_real_token(token, session) || compare_with_per_form_token(token, session, Request.new(env)) else false # Token is malformed end end
def xor_byte_strings(s1, s2)
def xor_byte_strings(s1, s2) s2 = s2.dup size = s1.bytesize i = 0 while i < size s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i)) i += 1 end s2 end