csrf protection in rails ( actioncontroller::requestforgeryprotection )

The Basics

There are two components to CSRF. First, a unique token is embedded in your site’s HTML. That same token is also stored in the session cookie. When a user makes a POST request, the CSRF token from the HTML gets sent with that request. Rails compares the token from the page with the token from the session cookie to ensure they match.
Alt text

Generation encrypted token and embedded into form

Alt text

Token generation(raw token) & Store into session

1
2
3
4
5
6


def (session) # :doc:
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
Base64.strict_decode64(session[:_csrf_token])
end

Token encryption

1
2
3
4
5
6
7
8
9
10
11
12
def masked_authenticity_token(session, form_options: {}) # :doc:
# ...
raw_token = if per_form_csrf_tokens && action && method
# ...
else
real_csrf_token(session)
end
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
masked_token = one_time_pad + encrypted_csrf_token
Base64.strict_encode64(masked_token)
end

Embeded into form

1
2
3
4
5
6
7
8
def csrf_meta_tags
if protect_against_forgery?
[
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
tag("meta", name: "csrf-token", content: form_authenticity_token)
].join("n").html_safe
end
end

then you’ll see:
Alt text

In the post params, get authenticity_token & decryption token and verify

Get masked_token

1
masked_token = Base64.strict_decode64(encoded_masked_token)

Get unmasked_token

1
2
3
4
5
def unmask_token(masked_token) # :doc:
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
xor_byte_strings(one_time_pad, encrypted_csrf_token)
end

Verify

1
2
3
def compare_with_real_token(token, session) # :doc:
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
end