Section 11 (Chapters 116 to 117) – CSRF Protection
Return to the Complex App Summary
Chapter 116 – Preventing CSRF
Cross-site-request-forgery
An innocent user of our application receives an email from a hacker that sends them to a totally different website and on this website is a seemingly harmless button.
Behind the scenes in the code this button is in a form element with an action attribute that points to our application’s “/create-post” route with hidden inputs that have value attributes set to malicious strings of text.
A scary thing about web browsers, there is nothing to stop this form from being submitted. Due to the nature of cookies this attack will be successful.
The innocent user actually doesn’t event need to click the button. There could also be JavaScript code that makes it submit automatically as soon as the page is visited, “document.querySelector(“form”).submit()”.
How and why is this actually working? Cookies are the culprit and cookies are to blame. When a user logs in the server tells the browser to store the id and on every subsequent request to that domain the web browser automatically send the cookie along with the request. Doesn’t matter the request comes from a different domain.
How can we adjust our application to prevent this sort of attack? Need to know what this attack can and cannot do.
What it can do is send traditional html form submits. Relies on an innocent user already being logged in to an existing application and then the malicious user sends the request to a known url with input fields they know are needed.
What it cannot do is see the application itself. Web browsers protect you on a tab to tab basis. It cannot read HTML or JavaScript on a different tab. So we can protect ourselves when the server sends html request to the browser we can also send a random string of characters and then set things up so that when a form is submitted you must also provide the random string not characters, or the request will fail. No way an outside source could get access to it. Random string proves the request is actually desired by the user.
The easiest way to implement install ‘csurf’ package and in the “app.js” file const csrf = require(‘csurf’) and leverage by adding, above the app.use(”/”, router) line, app.use(csrf()) to set things up so any request will need the.
Create a middleware function to make sure html templates have access to csurf. Under the code created above add app.use(function(req, res, next){res.locals.csrfToken=req.csrfToken() and then call next()})
Leverage this property in a view template by going into “create-post.ejs” and above submit button add an input with a type of hidden and a name of “_csrf” and a value of “<%= csrfToken %>”
Note that the name attribute bof the hidden input needs to be “_csrf” as required by the library.
On the front end you can inspect and you will see the hidden input and a value with a crazy long string of random characters. So by creating a post the post goes through successfully but the codepen example will no longer work.
Don’t want to show error message but a graceful flash message with a redirect.
Just under app.use(“/”, router) add a new middleware function:
app.use(function(err, req, res, next) {
if (err) {
if (err.code=="EBADCSRFTOKEN") {
req.flash("errors", "cross site request forgery detected")
req.session.save(() => res.redirect("/"))
} else {res.render("404") }
}})
This is great but work not done yet. If you try to do anything else, like signing out, you will get this error message. So you need to go through your application and anywhere you are sending a post request need to pass along that matching csrf token.
Chapter 117 – Adjusting Our App to Use CSRF Token
Csrf package demands a matching token for any post request. Copy code from “create-post.ejs” into clipboard and update “header.ejs” to add token for logout form, and add token to login form.
Note that when testing you need to refresh your front-end to make sure the new code is in there.
Do the same for “home-guest.ejs” on the registration form. You will notice that whatever you put as username you will still get “already taken” error and so we need to update the axios request to include the csrf token in registrationForm.js.
Create a property that grabs the csrf value from on of the hidden inputs on the page. Use querySelector and find based on the input name (this is a bit trickier than the usual id or class):
this._csrf = document.querySelector('[name="_csrf"]').value
find the userNameAfterDelay mehtod and in the data object add a property of “_csrf: this._csrf” and then find emailAfterDelay method and in the object that is being sent over add the “_csrf: this._csrf” property once again. Form should be good again.
Configure the search feature to support csrf by jumping into the search.js file and create same “this._csrf” property in the constructor (copy from registrationForm.js) and in the “sendRequest” method find the post request and add the “_csrf: this._csrf” property yet again.
Need to update the form in the “edit-post.ejs” template to have the hidden csrf input.
Do the same for the delete functionality by going into the “single-post-screen.ejs” template and find the form with class “delete-post-form.”
We also need to account for the “/addFollow/” and “removeFollow/” form actions. In views/includes folder go to the “profileShared.ejs” template.
We have successfully protected our application against csrf attacks.