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 to Forbidden</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 is server.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 application

nil
: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 the X-CSRF-Token header and the POST form
token matches the token included in the session.
HEAD, OPTIONS, TRACE if their given access
This middleware only accepts requests other than GET,
More infos
en.wikipedia.org/wiki/Cross-site_request_forgery<br>Supported browsers
all
Prevented attack

CSRF
#

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)

like BREACH.
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)

Essentially the inverse of +mask_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)

session 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