Skip to content

Commit 83574a7

Browse files
committed
Document Request.js in the guides [ci-skip]
With the removal of UJS as a default dependency, it can be unclear what to use for Ajax calls. The Rails Request.js is the official library to use in this case, so it should be documented in the guides.
1 parent 927b3b5 commit 83574a7

File tree

2 files changed

+89
-14
lines changed

2 files changed

+89
-14
lines changed

guides/source/security.md

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less th
250250

251251
NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._
252252

253+
#### Use GET and POST Appropriately
254+
253255
The HTTP protocol basically provides two main types of requests - GET and POST (DELETE, PUT, and PATCH should be used like POST). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:
254256

255257
**Use GET if:**
@@ -287,21 +289,52 @@ There are many other possibilities, like using a `<script>` tag to make a cross-
287289

288290
NOTE: We can't distinguish a `<script>` tag's origin—whether it's a tag on your own site or on some other malicious site—so we must block all `<script>` across the board, even if it's actually a safe same-origin script served from your own site. In these cases, explicitly skip CSRF protection on actions that serve JavaScript meant for a `<script>` tag.
289291

292+
#### Required Security Token
293+
290294
To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is done automatically when [`config.action_controller.default_protect_from_forgery`][] is set to `true`, which is the default for newly created Rails applications. You can also do it manually by adding the following to your application controller:
291295
292296
```ruby
293297
protect_from_forgery with: :exception
294298
```
295299
296-
This will include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown.
300+
This will include a security token in all forms generated by Rails. If the
301+
security token doesn't match what was expected, an exception will be thrown.
302+
303+
When submitting forms with [Turbo](https://turbo.hotwired.dev/) the security
304+
token is required as well. Turbo looks for the token in the `csrf` meta tags of
305+
your application layout and adds it to request in the `X-CSRF-Token` request
306+
header. These meta tags are created with the [`csrf_meta_tags`][] helper
307+
method:
308+
309+
```erb
310+
<head>
311+
<%= csrf_meta_tags %>
312+
</head>
313+
```
314+
315+
which results in:
316+
317+
```html
318+
<head>
319+
<meta name="csrf-param" content="authenticity_token" />
320+
<meta name="csrf-token" content="THE-TOKEN" />
321+
</head>
322+
```
323+
324+
When making your own non-GET requests from JavaScript the security token is
325+
required as well. [Rails Request.JS](https://github.com/rails/request.js) is a
326+
JavaScript library that encapsulates the logic of adding the required request
327+
headers.
328+
329+
When using another library to make Ajax calls, it is necessary to add the
330+
security token as a default header yourself. To get the token from the meta tag
331+
you could do something like:
332+
333+
```javascript
334+
document.head.querySelector("meta[name=csrf-token]")?.content
335+
```
297336

298-
NOTE: By default, Rails includes an [unobtrusive scripting adapter](https://github.com/rails/rails/blob/main/actionview/app/assets/javascripts),
299-
which adds a header called `X-CSRF-Token` with the security token on every non-GET
300-
Ajax call. Without this header, non-GET Ajax requests won't be accepted by Rails.
301-
When using another library to make Ajax calls, it is necessary to add the security
302-
token as a default header for Ajax calls in your library. To get the token, have
303-
a look at `<meta name='csrf-token' content='THE-TOKEN'>` tag printed by
304-
`<%= csrf_meta_tags %>` in your application view.
337+
#### Clearing Persistent Cookies
305338

306339
It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
307340

@@ -316,6 +349,7 @@ The above method can be placed in the `ApplicationController` and will be called
316349
Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read [more about XSS](#cross-site-scripting-xss) later.
317350

318351
[`config.action_controller.default_protect_from_forgery`]: configuring.html#config-action-controller-default-protect-from-forgery
352+
[`csrf_meta_tags`]: https://api.rubyonrails.org/classes/ActionView/Helpers/CsrfHelper.html#method-i-csrf_meta_tags
319353

320354
Redirection and Files
321355
---------------------

guides/source/working_with_javascript_in_rails.md

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,17 +241,18 @@ With a WebSocket connection set up on the page that should receive the updates l
241241
Replacements for Rails/UJS Functionality
242242
----------------------------------------
243243

244-
Rails 6 shipped with a tool called UJS that allows developers to override the method of `<a>` tags
245-
to perform non-GET requests after a hyperlink click and to add confirmation dialogs before
246-
executing an action. This was the default before Rails 7, but it is now recommended to use Turbo
247-
instead.
244+
Rails 6 shipped with a tool called UJS (Unobtrusive JavaScript). UJS allows
245+
developers to override the HTTP request method of `<a>` tags, to add confirmation
246+
dialogs before executing an action, and more. UJS was the default before Rails
247+
7, but it is now recommended to use Turbo instead.
248248

249249
### Method
250250

251251
Clicking links always results in an HTTP GET request. If your application is
252252
[RESTful](https://en.wikipedia.org/wiki/Representational_State_Transfer), some links are in fact
253-
actions that change data on the server, and should be performed with non-GET requests. This
254-
attribute allows marking up such links with an explicit method such as "post", "put", or "delete".
253+
actions that change data on the server, and should be performed with non-GET
254+
requests. The `data-turbo-method` attribute allows marking up such links with
255+
an explicit method such as "post", "put", or "delete".
255256

256257
Turbo will scan `<a>` tags in your application for the `turbo-method` data attribute and use the
257258
specified method when present, overriding the default GET action.
@@ -300,3 +301,43 @@ added to the form that the `button_to` helper renders internally:
300301
```erb
301302
<%= button_to "Delete post", post, method: :delete, form: { data: { turbo_confirm: "Are you sure?" } } %>
302303
```
304+
305+
### Ajax Requests
306+
307+
When making non-GET requests from JavaScript the `X-CSRF-Token` header is required.
308+
Without this header requests won't be accepted by Rails.
309+
310+
[Rails Request.JS](https://github.com/rails/request.js) encapsulates the logic
311+
of adding the request headers that are required by Rails. Just
312+
import the `FetchRequest` class from the package and instantiate it
313+
passing the request method, url, options, then call `await request.perform()`
314+
and do what do you need with the response.
315+
316+
For example:
317+
318+
```javascript
319+
import { FetchRequest } from '@rails/request.js'
320+
321+
....
322+
323+
async myMethod () {
324+
const request = new FetchRequest('post', 'localhost:3000/posts', {
325+
body: JSON.stringify({ name: 'Request.JS' })
326+
})
327+
const response = await request.perform()
328+
if (response.ok) {
329+
const body = await response.text
330+
}
331+
}
332+
```
333+
334+
When using another library to make Ajax calls, it is necessary to add the
335+
security token as a default header yourself. To get the token, have a look at
336+
`<meta name='csrf-token' content='THE-TOKEN'>` tag printed by
337+
[`csrf_meta_tags`][] in your application view. You could do something like:
338+
339+
```javascript
340+
document.head.querySelector("meta[name=csrf-token]")?.content
341+
```
342+
343+
[`csrf_meta_tags`]: https://api.rubyonrails.org/classes/ActionView/Helpers/CsrfHelper.html#method-i-csrf_meta_tags

0 commit comments

Comments
 (0)