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 2 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
25 changes: 1 addition & 24 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 - Making forms input into params
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of combining these two titles with a hyphen, I'd prefer to combine them together. Maybe something like this:

Suggested change
### Railsifying your form - Making forms input into params
### Railsifying your form by making the forms input into params

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated as proposed


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
46 changes: 43 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,34 @@ 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. CSRF Safety:
From 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 small 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using our markdown preview tool, I don't really like having the "CSRF Safety:" and the last sentence on different lines look. I propose rewording slightly to putting them all on 1 line, something like this:

Suggested change
1. CSRF Safety:
From 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 small 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. 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 small detour and disable Turbo for this form by setting the data attribute `data-turbo=false`. Then, in the dev tools network tab, compare the request type with and without the `data-turbo=false` attribute to confirm it works as expected.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated as proposed


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 +70,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 +111,20 @@ 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!?!
The from is now submitted with Turbo, yet Rails still protects you by verifying a CSRF token. Where does this token comes from?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of how these lines lay out in the markdown preview either, though I don't really have a great solution in mind. Maybe add an empty line between 120 and 121?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I have added an empty line to keep the "No more CSRF error" as a standalone observation that the reader should pause on. I did not put it on a separate numbered list item as it s not a new task.


1. Check your inspector and your `application.html.erb` template. See a CSRF token that s always available?
Remove this one too from `application.html.erb`, and verify that the server hits back with a CSRF error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo here: "that s always available?".

Also, I'm not a big fan of how this looks in the markdown preview. I would either put these two lines on the same line, or add an empty line in between (line lines 118-120).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated and put in a single paragraph.


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