Skip to content

Commit 1ff089d

Browse files
authored
Merge pull request #96 from inertiajs/csrf-via-middleware
Set up Rails CSRF to play nice with Axios default CSRF behavior
2 parents 09abe06 + 8b74371 commit 1ff089d

File tree

8 files changed

+64
-15
lines changed

8 files changed

+64
-15
lines changed
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
import { App } from '@inertiajs/inertia-react';
22
import React from 'react';
33
import { render } from 'react-dom';
4-
import axios from 'axios';
54
import { InertiaProgress } from '@inertiajs/progress';
65

76
document.addEventListener('DOMContentLoaded', () => {
87
InertiaProgress.init();
98
const el = document.getElementById('app')
109

11-
const csrfToken = document.querySelector('meta[name=csrf-token]').content;
12-
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;
13-
1410
render(
1511
<App
1612
initialPage={JSON.parse(el.dataset.page)}
1713
resolveComponent={name => require(`../Pages/${name}`).default}
1814
/>,
1915
el
2016
)
21-
});
17+
});
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import axios from 'axios'
2-
31
import { createInertiaApp } from '@inertiajs/inertia-svelte'
42
import { InertiaProgress } from '@inertiajs/progress'
53

64
document.addEventListener('DOMContentLoaded', () => {
7-
const csrfToken = document.querySelector('meta[name=csrf-token]').content
8-
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
9-
105
InertiaProgress.init()
116

127
createInertiaApp({
@@ -16,4 +11,4 @@ document.addEventListener('DOMContentLoaded', () => {
1611
new App({ target: el, props })
1712
},
1813
})
19-
})
14+
})

lib/generators/inertia_rails/install/vue/inertia.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import axios from 'axios'
21
import Vue from 'vue'
32

43
import { app, plugin } from '@inertiajs/inertia-vue'
54
import { InertiaProgress } from '@inertiajs/progress'
65

76
document.addEventListener('DOMContentLoaded', () => {
8-
const csrfToken = document.querySelector('meta[name=csrf-token]').content
9-
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
10-
117
InertiaProgress.init();
128
const el = document.getElementById('app')
139

lib/inertia_rails/controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ module Controller
1111
InertiaRails.share(errors: session[:inertia_errors]) if session[:inertia_errors].present?
1212
end
1313
helper ::InertiaRails::Helper
14+
15+
after_action do
16+
cookies['XSRF-TOKEN'] = form_authenticity_token unless request.inertia? || !protect_against_forgery?
17+
end
1418
end
1519

1620
module ClassMethods

lib/inertia_rails/middleware.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def initialize(app, env)
1818
end
1919

2020
def response
21+
copy_xsrf_to_csrf!
2122
status, headers, body = @app.call(@env)
2223
request = ActionDispatch::Request.new(@env)
2324

@@ -89,6 +90,10 @@ def force_refresh(request)
8990
request.flash.keep
9091
Rack::Response.new('', 409, {'X-Inertia-Location' => request.original_url}).finish
9192
end
93+
94+
def copy_xsrf_to_csrf!
95+
@env['HTTP_X_CSRF_TOKEN'] = @env['HTTP_X_XSRF_TOKEN'] if @env['HTTP_X_XSRF_TOKEN'] && inertia_request?
96+
end
9297
end
9398
end
9499
end

spec/inertia/request_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,40 @@
9696

9797
it { is_expected.to eq 302 }
9898
end
99+
100+
describe 'CSRF' do
101+
describe 'it sets the XSRF-TOKEN in the cookies' do
102+
subject { response.cookies }
103+
before do
104+
with_forgery_protection do
105+
get inertia_request_test_path, headers: headers
106+
end
107+
end
108+
109+
context 'it is not an inertia call' do
110+
let(:headers) { Hash.new }
111+
it { is_expected.to include('XSRF-TOKEN') }
112+
end
113+
114+
context 'it is an inertia call' do
115+
let(:headers){ { 'X-Inertia' => true } }
116+
it { is_expected.not_to include('XSRF-TOKEN') }
117+
end
118+
end
119+
120+
describe 'copying an X-XSRF-Token header (like Axios sends by default) into the X-CSRF-Token header (that Rails looks for by default)' do
121+
subject { request.headers['X-CSRF-Token'] }
122+
before { get inertia_request_test_path, headers: headers }
123+
124+
context 'it is an inertia call' do
125+
let(:headers) {{ 'X-Inertia' => true, 'X-XSRF-Token' => 'foo' }}
126+
it { is_expected.to eq 'foo' }
127+
end
128+
129+
context 'it is not an inertia call' do
130+
let(:headers) { { 'X-XSRF-Token' => 'foo' } }
131+
it { is_expected.to be_nil }
132+
end
133+
end
134+
end
99135
end

spec/rails_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
# Prevent database truncation if the environment is production
1010
abort("The Rails environment is running in production mode!") if Rails.env.production?
1111
require 'rspec/rails'
12+
# Require the spec/support directory and its subdirectories.
13+
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
14+
15+
require_relative './support/helper_module'
1216
# Add additional requires below this line. Rails is not loaded until this point!
1317
# Requires supporting ruby files with custom matchers and macros, etc, in
1418
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
@@ -45,6 +49,8 @@
4549
config.filter_rails_from_backtrace!
4650
# arbitrary gems may also be filtered via:
4751
# config.filter_gems_from_backtrace("gem name")
52+
53+
config.include HelperModule
4854
end
4955

5056
require 'rails-controller-testing'

spec/support/helper_module.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module HelperModule
2+
def with_forgery_protection
3+
orig = ActionController::Base.allow_forgery_protection
4+
begin
5+
ActionController::Base.allow_forgery_protection = true
6+
yield if block_given?
7+
ensure
8+
ActionController::Base.allow_forgery_protection = orig
9+
end
10+
end
11+
end

0 commit comments

Comments
 (0)