Skip CSRF token checking

When using SameSite Lax and Strict cookies, the main attack vectors that CSRF token mitigates are no longer present in modern browsers. The session cookie has defaulted to SameSite=Lax for a while now. If user’s browser itself is malicious, neither approach would provide meaningful protection; the token checking is a workaround for a common exposure in already trusted browsers.

Neither the CSRF token approach alone nor SameSite Lax protect against malicious or compromised subdomains. Instead, we have additional checks on the Origin and Referrer headers , and these remain important security checks.

As time moves on, we can expect that more and more users will be using browsers that support SameSite=Lax. As it currently stands, SameSite=Lax is supported by 96.61% of global usage according to caniuse.com.

I’d like to be able to remove the small bit of friction that is the requirement to add the csrf_token to forms in templates. While experienced Django users will instinctively remember to add this template tag, newcomers to Django can be bit by this, and there are times where this overhead is not worth the gain.

I’m not sure at what threshold of global usage it would be appropriate to disable the token checking by default for new projects, but it does seem to me that we are approaching that time where it provides diminishing returns to require it in order to receive the other protection that the CSRF middleware provides.

I propose adding a new setting CSRF_USE_TOKEN that defaults to True. When set to False, it would

  1. Make the {% csrf_token %} template tag a no-op.
  2. Skip token checking in the CSRF middleware.
  3. Skip setting the CSRF cookie.

All other CSRF-related checks of the Origin and Referrer headers would remain in place as they are currently.

I’m not in favour of even allowing this option. You linked to OWASP, which is good. However they themself still recommend using tokens, and mention using the SameSite attribute as a defense in depth technique, not as a primary mitigation. Until that changes (and perhaps even after), I think there is no reason to do this.

From the linked page:

It is important to note that this attribute should be implemented as an additional layer defense in depth concept. This attribute protects the user through the browsers supporting it, and it contains as well 2 ways to bypass it as mentioned in the following section. This attribute should not replace a CSRF Token. Instead, it should co-exist with that tokento protect the user in a more robust way.

Personally I don’t believe the tokens add very much extra friction at all, relative to their benefit.

One other thing I would add here. We’re a large framework. We have hundreds of millions, if not billions of end users around the world. 3% of this is still a very large number of people. It’s true we only support modern browsers, but this shouldn’t go so far that we needlessly jeopardise the security of tens of millions (or more) of people.

2 Likes

I think the tokens add a little friction. I sometimes forget to write {% csrf_token %} even after years of using Django.

That said I lean towards Tom’s position that it is not responsible to lean only on SameSite=Lax. The “in the following” section that Tom quoted links to a new cookie RFC. The latest version says:

Lax enforcement provides reasonable defense in depth against CSRF attacks that rely on unsafe HTTP methods (like POST), but does not offer a robust defense against CSRF as a general category of attack:

  1. Attackers can still pop up new windows or trigger top-level navigations in order to create a “same-site” request (as described in Section 5.2.1), which is only a speedbump along the road to exploitation.

  2. Features like <link rel='prerender'> [prerendering] can be exploited to create “same-site” requests without the risk of user detection.

When possible, developers should use a session management mechanism such as that described in Section 8.8.2 to mitigate the risk of CSRF more completely.

So the new cookie specification continues to recommend tokens, and I think we should follow that, along with OWASP’s recommendation.

If you want we could invite other members of the security team into the thread. Unfortunately, it doesn’t seem there’s a convenient way to @-mention everyone right now.

1 Like

@tom and @adamchainz, thank you both for your thoughts, the quotes, and the links, and sorry for the novel I’m throwing at you now.

I’ll try to address some comments in ways that I hope are helpful to the discussion, before I launch into a pretty lengthy analysis of this OWASP recommendation and the references they used to support it.

Personally I don’t believe the tokens add very much extra friction at all, relative to their benefit.

I think the tokens add a little friction.

I agree with both of you. The tokens add relatively little friction, and historically that small amount of fiction has been well worth the benefit they provide. However, if the benefit is reduced, it is warranted to reconsider whether the benefit continues to outweigh the cost of the friction.

Even small amounts of unnecessary friction can hinder those we most want to join our community. We value diversity, and removing the friction to begin and grow is critical to enabling diversity to join our community. This is only one small papercut, but if we remove these small papercuts one at a time we end up with a framework that has spent a lot of time caring about the people who may otherwise be left out or frustrated if we did not.

So the new cookie specification continues to recommend tokens, and I think we should follow that, along with OWASP’s recommendation.

I believe this is a misread of this section, and one that I initially made too as I was digging in. I go into greater detail below, but succinctly, this section isn’t talking about CSRF tokens, it’s talking about Strict vs Lax enforcement and an interesting “two-cookie” mechanism for separating read authorization and write authorization.

One other thing I would add here. We’re a large framework. We have hundreds of millions, if not billions of end users around the world. 3% of this is still a very large number of people. It’s true we only support modern browsers, but this shouldn’t go so far that we needlessly jeopardise the security of tens of millions (or more) of people.

This is a very wise consideration, and is the crux of the judgement call we need to make here. We must determine if the benefit is worth the risk to those using our framework and the people who use Django-powered sites.

I did attempt to be modest in my proposal, though. I do not think that this approach is one we want to encourage everyone to make, especially not immediately. While I can imagine us encouraging it more as time progresses and SameSite cookies become ubiquitous, and I don’t want to pretend that will never happen, I really don’t know what kind of timeframe would be appropriate for that, if any, or how we should make that determination.

What I’m proposing is that I, and those that believe they are like me, have the ability to make this assessment without needing to rewrite parts of Django in order to get the other benefits that are included in our CSRF protection, namely the Origin and Referrer header checking.

Now, it could be that even this is unwise. That given this lever, too many people would be inclined to pull the lever even when it could cause them real problems. I’m not sure that’s the case, and if we are to ever get to relying on Lax enforcement we’ll have to do it eventually, but perhaps now isn’t the time to introduce the lever. It seems to me likely that we’d want to get there, if for no other reason than that if browser support were ubiquitous, as we expect it to be at some point in the future, we would likely not have compelling reason to include this feature in Django core.

If now isn’t the time because 3% is still too dangerous, I’d appreciate a better understanding of what would be an acceptable percentage of global users that are on browsers that don’t support SameSite=Lax to where it would be the right time to add the lever. I really don’t have a good sense of what Django should think about that question.

I wonder though, with the clearer target of removing the need to include the {% csrf_token %} template tag, perhaps an alternative proposal could be more modest yet than my original:

  • Add a new setting CSRF_AUTO_CHECK_TOKEN that defaults to True
  • When False would not check the CSRF token for views not decorated explicitly with @csrf_protect

I’m not sure that would alleviate your concerns sufficiently, but it would mean that things that explicitly annotate CSRF protection at the view level, like the Django admin, would still check the CSRF token.

If you want we could invite other members of the security team into the thread.

I don’t feel terribly comfortable doing that myself, but I’m all for having more brains looking at this idea that has been taking (probably too much) space in my mind for a while now.


OK, now for the OWASP recommendation analysis.

The OWASP recommendation gives two reasons for its recommendation against relying on SameSite cookies in lieu of CSRF tokens.


The first is the prevalence of browser support (“protects the user through the browsers supporting it”). Depending on your reading of it, this can be referring to an illegitimate concern, or one that we can reasonably expect to be reduced over time. I think the latter is the correct reading, but for the sake of others who may read this later and learn a bit about CSRF from this discussion, I do want to address the former anyway.

One way to read this concern of browser support is that if the browser isn’t secure, then there’s nothing that the server can do to mitigate CSRF attacks. Put in this simplistic way, it’s important to recognize that this is exactly the same as things would be with server-side CSRF checking. Either way, we very much rely on the security afforded by browsers. If the browser itself were untrustworthy, there is no amount of server-side patching we could ever do to make it safe for an end-user to use. Such is the state of any site that allows connections from clients that are not verified, and I think we have good reasons that we generally do not require that type of strict verification of browsers.

But again, I do not think this is what they meant: they meant that users are still using browsers that do not implement SameSite cookies. By its nature, we can expect that over time this concern will increasingly grow less relevant. For some this transition of their user base will go quickly, but for others their user base may more commonly be on older browsers.

This is a substantive concern, and is where the legitimate concerns that I’m aware of are located.


The second reason that OWASP gives are the “2 ways to bypass it as mentioned in the following section”. After having read that section and related sections to do my best to make sure I understand it correctly, I think that these “ways to bypass it” do not provide justification for this stance from Django, and I’m not sure they provide additional justification for this stance at all.

Before I explain why, I’ll note that even if OWASP is wrong about the security implications, the fact that OWASP makes the recommendation is something that Django should take into consideration. It’s just that the specifics of the choices we’re looking at make these “ways to bypass it” not relevant to the actual implementation in Django.

The section pointed to in the OWASP article is entitled “‘Strict’ and ‘Lax’ enforcement”. As the title might suggest, the purpose of the section is to discuss the relative strengths and weaknesses of using each of these modes for CSRF protection. I think that understanding this section on its own terms is important to evaluating the concerns raised by the OWASP recommendation.

In discussing the challenges associated with “Strict” enforcement, the authors point out that “Lax enforcement provides a reasonable defense in depth against CSRF attacks that rely on unsafe HTTP methods (like POST)”. Like most security-related documents, they are actively avoiding making recommendations against dropping other types of security, but it’s important to note that they are not making any particular statement about traditional CSRF tokens, and especially not saying that traditional CSRF tokens solve problems that SameSite does not.

Then they point out two particular areas of potentially confusing CSRF concern that “Lax” brings up in comparison with “Strict” enforcement: top-level navigation and pre-rendering. In both cases, these concerns would, as far as I can tell, apply equally to Django’s existing CSRF implementation using a token. Only a CSRF implementation analogous to “Strict” enforcement would alleviate the concerns presented here, and Django has made the conscious choice to align our CSRF protection the same way that the “Lax” enforcement does: based on “safe” HTTP methods.

They do recommend using “a session management mechanism such as described in Section 8.8.2”. Because that sounds vaguely like a recommendation to use a CSRF session token, I want to follow that thread, and see what they’re suggesting. Turns out, it’s kind of interesting, but also pretty irrelevant to the question of whether SameSite=Lax has some drawback compared to a CSRF token.

It’s worth a read, but essentially it’s suggesting using a “Lax” session cookie for read-only access, and a separate “Strict” session cookie that grants write access. It’s most important to note that it is not making any suggestion that a session CSRF token is still necessary. We can also note that this pattern is unrelated to the choice of using a CSRF token or relying on SameSite cookie enforcement.

I don’t quite agree that this section isn’t talking about CSRF tokens, because it explicitly uses the phrase (like I showed in my previous quote) “CSRF Token”.

This attribute should not replace a CSRF Token

If this is somehow talking about a CSRF token that is meant in a different way than we do (and indeed in a different way than earlier in the same page), I would find this very confusing.

I was unclear in my response. The OWASP article is talking about a CSRF token. The section they link to in the cookie RFC, which had a phrase that @adamchainz was particularly quoting and commenting on, is not about a CSRF token. I believe the OWASP authors may have misunderstood the purpose of the section of the spec they referenced.

If I squint hard enough, I can imagine a future where Django’s CSRF token was how we attempt to get to the RFC’s suggestion of two tokens, by marking the CSRF token as SameSite=Strict, possibly by default. But that’s not how Django’s CSRF token works right now. And we don’t block “write access” based on the CSRF token, and I suspect moving to do that is a non-starter, in no small part because apart from the safe/unsafe method distinction (the key differentiation between Lax and Strict), I think we’d have a hard time coming to a consensus on what should fall in the category of “write access” and it would probably be deemed out of scope for the CSRF middleware.

marking the CSRF token as SameSite=Strict , possibly by default.

Thinking more on this, I don’t think is reasonable, because we need to read the CSRF token in order to write the CSRF token in forms. So we’re back to where we started, where the CSRF token isn’t, and can’t be, the two-cookie approach the RFC authors suggested.

Hi @ryanhiebert.

Thanks for this. I suspect there is a day when the CSRF token can be dropped but I don’t think it’s yet.

Echoing Tom’s point, I think the OWASP recommendation is clear:

It is important to note that this attribute should be implemented as an additional layer defense in depth concept. This attribute protects the user through the browsers supporting it, and it contains as well 2 ways to bypass it as mentioned in the following section. This attribute should not replace a CSRF Token. Instead, it should co-exist with that token to protect the user in a more robust way.

I really struggle to try and find a reading of that that suggests otherwise.

Reading then your analysis, you can colour me sceptical. It’s anything but clear, and the thought of rolling out (and educating users) about multiple cookies for differing request methods kind of makes me shudder. Maybe, technically, if you do it just right, that’s a step up even — but we want the best all-round secure by default approach, and I can’t see how NOT following the OWASP recommendation gives us that.

As and when OWASP update their advice, I’d be happy to revisit this.

As an aside, as we discussed on a fedi-thread, I’ve suspect that most folks are hitting CSRF errors due to the origin checking (and request.is_secure() returning False) rather than from a missing CSRF token per se. (I occaisionally write a form and forget to add the template tag but I hit this immediately in development and fix it. The Origin is harder because I don’t see that until I deploy.)

I really struggle to try and find a reading of that that suggests otherwise.

Your reading is right, and OWASPs recommendation is unambiguously clear that you should use both the token and SameSite cookies. The only misread I suggested was of the Cookie RFC, which I believe was misunderstood by the OWASP authors in their analysis.

multiple cookies for differing request methods kind of makes me shudder.

It’s a non-starter as far as the CSRF token goes, because it doesn’t map to the CSRF token. I agree with you that it sounds like a royal pain to work with in practice.

most folks are hitting CSRF errors due to the origin checking

Yes, totally agreed. I started this conversation, so I wanted to finish it in its own right, but that’s another conversation that I intend to start soon. I’m doing some more research so that I can have an actual proposal with hopefully some amount of clear reasoning before I do so.

1 Like

Thank you all for your engagement. If possible, before I give up for an unspecified amount of time, I’d like to take a moment to clarify my latest proposal, as it may have gotten lost in the long message I wrote, as well as make sure we understand exactly what we’re saying. Then I’ll introduce one alternative that might make a similar choice available, but in a different box that might feel more reasonable.

The latest proposal I made is to add a setting, CSRF_AUTO_CHECK_TOKEN that defaults to True and keeps the current behavior. When that setting is set to False, undecorated views would not check for the CSRF token in the middleware, but there would be no effect to any views using the csrf_protect decorator, and the CSRF token would continue to be present and checked for those views.

It’s important that, because the CSRFViewMiddleware is already something that can be removed from the MIDDLEWARE setting, that this is not, as it might seem, about allowing them to skip the CSRF token checking. Instead, it’s about allowing them to use the other protections currently in the CSRFViewMiddleware, like strict origin checking, without also requiring them to use the CSRF token.

We could consider a different spelling, if we weren’t opposed to separating these concerns in principle, but are just questioning how to do it in practice. We could consider splitting out the origin (and referrer?) checking into a different middleware, and have the CSRF middleware require or depend on that new middleware.


If we were writing CSRF without the history we do (and that history is also our success), I suspect that we might not add the CSRF token-based protection to Django with this bi-directional coupling required. That assessment makes me feel more comfortable with the notion of allowing this to be partially decoupled.

I do not aim to recommend people disregard OWASPs recommendation, even if I find it annoying that some of their motivations for the recommendation seem to be based on a misunderstanding, and hope they clear that up soon. I do aim to make it a little easier for us to make things easier for beginners in situations where that is appropriate, and I am driven to try to help that world materialize sooner than later.


With the understanding that it is already possible to disable CSRF protection, do we still maintain that it’s important to require that these protections are tightly coupled in both directions? If loosening the coupling in one direction could be acceptable, could having a separate middleware that did the origin and referrer checking be acceptable, or might there be another approach that could be acceptable?

I’m not sure what to think of the last proposal there. (Need to think about it, quite a lot I suspect. I still have more confidence in the guidance than our reasoning about how it shouldn’t apply in case x or y.)

I’m struggling though to see the CSRF token as such a burden that it’s worth removing at this stage. I go back to that I sometimes forget to add it to a template but then spot that immediately in development. I’m not seeing that the extra knobs buy us very much, but potentially cost a lot (when misused as they certainly would be, somewhere).

1 Like

I’m struggling though to see the CSRF token as such a burden that it’s worth removing at this stage.

Thank you for considering this proposal, and if there’s no further conversation here I don’t intend to make any more nuisance of myself than this.

I’m confident you understand, so this is just a quibble on framing. The way you’ve stated it just here feels backward, since this isn’t a conversation about removing the CSRF token checking from Django, even by any measure of defaults like the default project template (that’s a longer, harder conversation that it’s not the right time for yet).

We should be asking if the CSRF token is important enough that we want to actively impede its disuse by coupling it with these other security features that we expect to live longer than this one. That analysis is rightly related to how much of a burden it is to use, and you’re right to say that its a relatively small burden.

Hey @ryanhiebert.

Is it important? I think we just have to follow the OWASP guidance here. (If that guidance should be updated the place for that discussion is over there, but I don’t think deciding by ourselves to go off piste is a good direction.)

But let’s grant that a small number of folks might be sufficiently informed and benefit from a switch (which I don’t really think: just use it, is might actual position). In that case we have a setting which everybody has to read, understand, and most likely decide doesn’t apply to them. It’s going to have to have big warnings about how it’s not really recommended, and so on. That’s a cognitive burden for every user, adding something which is a minor optimisation for a very few. It increases the API surface area for no great benefit, and we shouldn’t do that. Could we have a switch, yes maybe. Should we add it, I think no.

I hope that makes sense. It’s only my take, but in these kind of things, I lean towards keeping things simple (and pointing users in the right direction) rather than adding complexity that’s likely to go unused.

1 Like

I don’t think that having a setting to turn on/off the token stuff is a good idea. If I where to do something like this (and I am not saying we should) then I would probably introduce something along the lines of CSRF_MIDDLEWARE=some_middleware that would be used by @csrf_protect as well as being wrapped by the existing middleware. This way the behavior becomes completely user defined and both the decorator and middleware behave the same. I just don’t think that the increased complexity of such a redirection + restructuring of the existing middleware (assuming some customization hooks are missing) is worth it for a security related feature.

But I am not opposed to get rid of the two ken completely once there is no real need for it anymore.

1 Like

When we can drop CSRF tokens, we will be able to avoid the double login failure.