Digging deeper into CSRF and Front-End applications

Max Shahdoost
9 min readSep 13, 2023

--

Cross-Site Request Forgery

A couple of days ago I posted content about how to secure our web application from XSS attacks. In this post, I am going to cover yet another important security vulnerability known as Cross-Site Request Forgery abbreviated by CSRF in the web industry.

What happens is that the perpetrator will create a malicious link and will send it to your e-mail or expose it somewhere, Another kind of attack is that the perpetrator will create an innocent website that contains interesting data for you but the exploit is within it as well.

The first form of CSRF attack

In this case, the perpetrator will send a request by knowing that you are using the targeted service and you have cookies in your browser available for that service like a social account or bank service and that request contains the cookie information necessary to do actions on your behalf and this way the attacker could do damage depending on the type of exploit in the service to you as a valid user by stealing your cookies!

The second form of CSRF attack

What are the Impacts?

Depending on the functionalities of the application it can defer a lot but mainly these are the possible damages:

1- Confidentiality (None, Partial or High).

2- Integrity((Partial or High).

3- Availability((None, Partial or High).

What can we do to stop or reduce the vulnerabilities to CSRF?

Well, this is going to be a long story but I would try to make it short, There are multiple ways of securing a client web application.

How to prevent CSRF vulnerabilities

Use Anti-CSRF token with Refresh Token

The very common and well-known solution to implement to reduce the surface of being CSRF attacked is not to store sensitive information on cookies at all!

Store the token in the memory of your application for example if you are using React, you can do it in a Context or Redux or whatever tool you use for your client-side memory storage.

How should CSRF tokens be generated?

Configure your server to set HTTPonly cookie along with secure and sameSite: Strict flags in the login process, this way The cookie will only be accessed by the server-side and can only be sent with requests being dispatched from the same domain only when the user has logged in.

httpOnly: true, secure: true, sameSite: strict

Now keep in mind that the same site has a story of its own so let’s go deeper in this favor to know the cookie structures better.

Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<number>
Set-Cookie: <cookie-name>=<cookie-value>; Partitioned
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure

Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=None; Secure

// Multiple attributes are also possible, for example:
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly

The same parameter in a cookie controls whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks. Let’s check each possible value and their use case:

1- sameSite: Strict
This means that the browser sends the cookie only for same-site requests, that is, requests originating from the same site that set the cookie. If a request originates from a different domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute is sent.

2- sameSite: Lax

This means that the cookie is not sent on cross-site requests, such as on requests to load images or frames, but is sent when a user is navigating to the origin site from an external site (for example, when following a link). This is the default behavior if the SameSite the attribute is not specified.

3- sameSite: None

This means that the browser sends the cookie with both cross-site and same-site requests. The Secure attribute must also be set when setting this value, like so SameSite=None; Secure

Assume you are using REST APIs and your back-end is deployed on a subdomain or another domain rather than the front-end domain how can we check if we can set a sameSite or not before we set the cookie rules?

Regardless of the Domain attribute of a cookie, two sites are considered the same when their eTLD+1 (aka registrable domain) are the same.

If “domain.com” is on the public suffix list, then subdomain1.domain.com and subdomain2.domain.com are considered different sites. Otherwise, they are considered the same site.

The relevant notion of “site” when it comes to SameSite cookies is the eTLD+1 (effective Top Level Domain + 1 label). An effective Top Level Domain is something like .com or .co.uk or .github.io. All the eTLD’s are listed on the public suffix list.

An eTLD+1 is the effective Top Level Domain plus the 1 label immediately to its left. The eTLD+1 is also called the “registrable domain”. The intuition is that two different eTLD+1’s are controlled by different entities, and everything that is a subdomain of the same eTLD+1 is controlled by the same entity. For example, mysite.github.io is a different eTLD+1 than yoursite.github.io, because I cannot modify your site, and you can’t modify mine. On the other hand, the same company owns both subdomain1.domain.com and subdomain2.domain.com and modify both sites.

If the eTLD+1’s are the same for two domain names, they are considered the same site for the purposes of SameSite cookies.

What does the Domain attribute in the cookie mean then?

If a cookie is set with Set-Cookie: cookiename=cookievalue; Domain=mysite.com, then the cookie will be sent on requests to any domain matching *.mysite.com (i.e. all subdomains).

This is a way to adjust the scope of a cookie. For example, you could use Domain=mysite.com for a global cookie that all of your domains care about, and Domain=corp.mysite.com for a cookie that all of your company's internal domains care about (but not your external-facing domains, for example).

The default (for cookies that don’t explicitly set a Domain attribute) is that cookies are sent only to the domain that set the cookie. (No subdomains.)

You cannot set a Domain attribute that does not match the URL of the request. Also, there is no such thing as an “origin” attribute of a cookie.)

Understanding “same-site” and “same-origin”

“same-site” and “same-origin” are frequently cited but often misunderstood terms. For example, they are mentioned in the context of page transitions, fetch() requests, cookies, opening popups, embedded resources, and iframes.

URL structure

“Origin” is a combination of a scheme (also known as the protocol, for example, HTTP or HTTPS), hostname, and port (if specified). For example, given a URL of https://www.example.com:443/foo , the "origin" is https://www.example.com:443.

Websites that have the combination of the same scheme, hostname, and port are considered “same-origin”. Everything else is considered “cross-origin”.

same-origin comparison
scheme + TLD + 1

Top-level domains (TLDs) such as .com and .org are listed in the Root Zone Database. In the example above, "site" is the combination of the scheme, the TLD and the part of the domain just before it (We call it TLD+1). For example, given a URL of https://www.example.com:443/foo , the "site" is https://example.com.

For domains that include things such as .co.jp or .github.io, just using .jp or .io is not granular enough to identify the "site". There is no way to algorithmically determine the level of registrable domains for a particular TLD. That's why a list of public suffixes defined in the Public Suffix List was created. These public suffixes are also called effective TLDs (eTLDs). The list of eTLDs is maintained at publicsuffix.org/list.

To identify the “site” part of a domain that includes an eTLD, apply the same practice as the example with .com. Taking https://www.project.github.io:443/foo as an example, the scheme is https, the eTLD is .github.io and the eTLD+1 is project.github.io, so https://project.github.io is considered the "site" for this URL.

scheme + eTLD + 1

Websites that have the same scheme and the same eTLD+1 are considered “same-site”. Websites that have a different scheme or a different eTLD+1 are “cross-site”.

same-site comparison

Now you are confident to tell whether your back-end is considered same-site to your front-end or not before you try to use Anti-CSRF tokens.
For more details on how to implement this solution please check my comprehensive implementation of anti-CSRF/XSS React.js client-side authentication linked in the below resources part.

Security and Convenience: The Clashing UX Requirements of Authentication

When designing the authentication user experience, convenience and level of security are often at odds.

More Secure

  • Secondary Authentication Method (2FA)
  • No persistent login ‘remember me’ (closing the tab/browser logs the user out)
  • Inactivity timeout (leaving the tab/window open but not using the site for X amount of time logs the user out)

More convenient for the user

  • Username and password only
  • Remain logged in across browser sessions
  • Remain logged in indefinitely regardless of activity

According to the above explanation you may ask, what is the point of keeping the token in the memory when the users have to call the refresh-token route every time they close the tab and re-open it or do a hard refresh? well, that is a valid question.

Let’s look at the drawbacks of our anti-CSRF tokens

1- If the auth service is down and the refresh request fails the user is kicked out!

2- If a refresh-token request takes time to resolve, it affects UX badly.

3- If the user base of the application is high, it means more load on the server which is relative by the way because not all the users will do a hard refresh.

Depending on the needs of the business and the user experience standards things can vary a lot, but knowing the root causes of a CSRF attack and preventing it by the means at hand is the very root of each solution that you will provide for your own case and I focused to share that part other than more than anything in this article.

For example, you can set a strong CSP policy for your web application to prevent XSS along with strict sameSite cookies with secure options with a little bit of encryption and you will be fine!

Another example is to use JWT tokens divided into two parts, its signature in httpOnly, and the rest in a normal JS-accessible cookie.

I hope this article could be helpful to you, Please feel free to comment and ask questions.

Good Resources to read:

https://hasura.io/blog/best-practices-of-using-jwt-with-graphql

--

--

Max Shahdoost

A highly passionate and motivated Frontend Engineer with a good taste for re-usability, security, and developer experience.