Skip to content

Commit 4e41701

Browse files
authored
Merge pull request rails#47365 from p8/guides/document-request-js
Document Request.js in the guides [ci-skip]
2 parents 450e338 + 83574a7 commit 4e41701

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
@@ -238,17 +238,18 @@ With a WebSocket connection set up on the page that should receive the updates l
238238
Replacements for Rails/UJS Functionality
239239
----------------------------------------
240240

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

246246
### Method
247247

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

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

0 commit comments

Comments
 (0)