Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RoR forms project - Update CSRF token related sections #29165

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions ruby_on_rails/forms_and_authentication/form_basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,7 @@ The first line tells us which HTTP method was used and which route the form went

You'll find yourself looking at this server output a lot when you start building forms. It'll keep you sane because it tells you exactly what the browser sent back to your application so you can see if there's been a... misunderstanding.

### Railsifying your form

The first thing you'll realize if you try to create a plain vanilla form in a Rails view is that it won't work. You'll either get an error or your user session will get zeroed out (depending on your Rails version). That's because Rails by default automatically protects you from [cross-site request forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) and it requires you to verify that the form was actually submitted from a page you generated. In order to do so, it generates an ["authenticity token"](http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf) which looks like gibberish but helps Rails match the form with your session and the application.

You'll notice the token in the server output from above:

```bash
...
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "email"=>"foo@bar.com", "commit"=>"Submit Form"}
```

So, if you want to create your own form that gets handled by Rails, you need to provide the token somehow as well. Luckily, Rails gives you a method called `form_authenticity_token` to do so, and we'll cover it in the project.

```erb
<input
type="hidden"
name="authenticity_token"
value="<%= form_authenticity_token %>"
>
```

### Making forms into params

What about the other form inputs, the ones we actually care about?
### Railsifying your form by making forms input into params

Each one of these inputs is structured slightly differently, but there are some commonalities. One important thing to note is the `name` attribute that you can give to an input tag. In Rails, that's very important. The `name` attribute tells Rails what it should call the stuff you entered in that input field when it creates the `params` hash. For instance,

Expand Down Expand Up @@ -299,9 +276,8 @@ At this point, you should have a solid understanding of how forms work in genera

The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge.

- [What is a CSRF Token and why is it necessary?](#railsifying-your-form)
- [What is the `name` attribute of a form input element and what does it do?](#making-forms-into-params)
- [How do you nest attributes under a single hash in `params`?](#making-forms-into-params)
- [What is the `name` attribute of a form input element and what does it do?](#railsifying-your-form-by-making-forms-input-into-params)
- [How do you nest attributes under a single hash in `params`?](#railsifying-your-form-by-making-forms-input-into-params)
- [How do you pass `form_with` a model object?](#using-models-with-the-form_with-helper)
- [How do you access errors for a failed-to-save model object?](#forms-and-validations)
- [How do Rails forms make PATCH or DELETE requests?](#making-patch-and-delete-submissions)
Expand Down
43 changes: 40 additions & 3 deletions ruby_on_rails/forms_and_authentication/project_forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,33 @@ The first form you build will be mostly HTML (remember that stuff at all?). Bui

1. Build a form for creating a new user. See the [W3Schools page for forms](https://www.w3schools.com/tags/tag_form.asp) if you’ve totally forgotten how they work. Specify the `method` and the `action` attributes in your `<form>` tag (use `$ rails routes` to see which HTTP method and path are being expected based on the resource you created). Include the attribute `accept-charset="UTF-8"` as well, which Rails naturally adds to its forms to specify Unicode character encoding.

You don't want to forget about safety, so make sure you provide the form with an authenticity token. If you don't remember how to do so, go back to the [Form Basics lesson](https://www.theodinproject.com/lessons/ruby-on-rails-form-basics#railsifying-your-form) and refresh your memory.

1. Create the proper input tags for your user's fields (email, username and password). Use the proper password input for "password". Be sure to specify the `name` attribute for these inputs. Make label tags which correspond to each field.
1. Submit your form and view the server output. You will see nothing happening, no error message, nothing. If you look at the network tab in your inspector or at your server log, you can see that a request was issued, but a response of `204 No Content` is returned.

1. For CSRF safety with Rails 7, Turbo is enabled by default in new apps. Turbo intercepts form submission and makes a partial XHR request instead of a standard HTTP request with full page reload. To get a better grasp of Rails protection against [cross-site request forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery), let's take a short detour and disable Turbo for this form by setting the data attribute `data-turbo=false`.
In the dev tools network tab, compare the request type with and without the `data-turbo=false` attribute to confirm it works as expected.

1. Submit your form and view the server output. The request should be intercepted before reaching your controller and the server will throw a CSRF error `ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.)`.

That's because Rails by default automatically protects you from [cross-site request forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) and it requires you to verify that the form was actually submitted from a page you generated. In order to do so, it generates an ["authenticity token"](http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf) which looks like gibberish but helps Rails match the form with your session and the application.

So, if you want to create your own form that gets handled by Rails, you need to provide the token somehow as well. Luckily, Rails gives you a method called `form_authenticity_token` to do so

```erb
<input
type="hidden"
name="authenticity_token"
value="<%= form_authenticity_token %>"
>
```

You'll now notice the token in the server output:

```bash
...
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "email"=>"foo@bar.com", "commit"=>"Submit Form"}
```

1. However, if you look at the server output, you will see nothing much happening after the log of parameters received. The log should indicate a completed response with status 204 (no content). And indeed, if you look at the network tab in your inspector, you can see that a request was issued, but a response of `204 No Content` is returned.
1. That's A-OK because it means that we've successfully gotten through our blank `#create` action in the controller (and didn't specify what should happen next). Look at the server output. It should include the parameters that were submitted, looking something like:

```bash
Expand All @@ -46,6 +69,8 @@ The first form you build will be mostly HTML (remember that stuff at all?). Bui

That looks a whole lot like what you normally see when Rails does it, right?

#### Controller setup

1. Go into your UsersController and build out the `#create` action to take those parameters and create a new User from them. If you successfully save the user, you should redirect back to the New User form (which will be blank) and if you don't, it should render the `:new` form again (but it will still have the existing information entered in it). You should be able to use something like:

```ruby
Expand Down Expand Up @@ -85,6 +110,18 @@ Now we'll start morphing our form into a full Rails form using the `#form_tag` a
1. Test out your form. You'll need to change your `#create` method in the controller to once again accept normal top level User attributes, so uncomment the old `User.new` line and comment out the newer one.
1. You've just finished the first step.

#### Turn Turbo back ON

Above, we asked to disable Turbo for the sake of the exercise.

1. Re-enable form submission with Turbo by removing the `data-turbo=false` attribute on the form tag, then also remove the hidden input with CSRF token tag and submit.

No more CSRF error?!

1. The from is now submitted with Turbo, yet Rails still protects you by verifying a CSRF token. Where does this token comes from? Check your inspector and your `application.html.erb` template. Can you find a CSRF token that is always available? Remove this one too from `application.html.erb`, and verify that the server hits back with a CSRF error.

1. Reinstate the CSRF token tag in both places and carry on.

#### Railsy-er forms with #form_with

`#form_tag` probably didn't feel that useful -- it's about the same amount of work as using `<form>`, though it does take care of the authenticity token stuff for you. Now we'll convert that into `#form_with`, which will make use of our model objects to build the form.
Expand Down