2

I am using Django REST framework.

I want a single API for all of my clients (web, mobile, curl).

I understand that I need to include a CSRF token in requests originating from the web client, to protect against CSRF. However, this is not needed for mobile and curl clients.

How can I securely differentiate between client types so I can require a CSRF token for web clients but not for all other clients?

I have thought about the following:

  • Checking Origin and Referer headers. Django's CSRF middleware already does this. However, is it safe to assume not having these headers means the request has not come from the web client?
  • Having an unauthenticated CSRF endpoint that returns a valid token. However, this is inconvenient to use for clients such as curl. Also, I think it makes the whole CSRF protection useless since an attacker can simply request a token before making a malicious request.
1

1 Answer 1

4

Use a custom authentication header for the non-browser clients. For example, transmit an API key with an HTTP header like X-API-Key. A client like curl can easily set this, but a browser will never automatically send such a header, which reliable prevents most CSRF attacks. The only cases where this doesn't help is a combination of XSS and CSRF, or a completely broken CORS configuration on your site – then an attacker can make an Ajax request and include the header.

If you receive an X-API-Keyheader with a valid API key, you can accept the request with no CSRF checks. If a client tries to authenticate with a cookie or an Authorizaton header, then you need an additional CSRF check (e.g., by requiring an anti-CSRF token in the request).

The Referer header is often suppressed for privacy reasons, so you cannot rely on it. Checking the Origin header is slightly better, but it's still not always sent.

OP EDIT:

To further clarify, for login CSRF prevention, when there is no auth token to be provided via a custom header, two methods can be used:

  • Require the authentication header (or another custom header) to be still present but take the login credentials from the request body
  • Provide the login credentials via a custom header. Similar to HTTP basic auth.

Finally, in all cases - make sure to only use unsafe HTTP methods (as defined by RFC 9110), such as 'POST', as preflight checks only apply to unsafe methods.

7
  • Yes that may work for authorized flows, but what about a login flow (i.e. to prevent login CSRF attacks? There is no token yet, therefore there is no need to set a custom header. While i can still require an arbitrary header to be set to skip CSRF, I would like to avoid this if possible. I should mention that I intend to use JWT for all client types, so I cannot differentiate based on auth type. Commented Feb 24 at 4:23
  • Also, from the link you provided, do I understand correctly that the Origin header value may be null for a lot of reasons but is still sent to the server? If I understand correctly, all cases where it may be missing entirely as a header are for GET and HEAD requests, which CSRF is not designed to protect anyway as they are considered to be 'safe' requests and should not be changing server state. Commented Feb 24 at 4:42
  • @Slav: JWT isn't an authentication type, it's just a token format. You may very well put a JWT token into a custom header. In fact, you can also do this with Ajax in a browser. A CSRF-safe log-in isn't an issue either, because you can put the credentials needed to get a token into a custom header as well. There's no rule which says they must be submitted through URL parameters or within the request body. Commented Feb 24 at 4:46
  • @Slav: The only case which would require a different approach is if you wanted to submit a classical HTML form directly to a REST endpoint using a browser. Does this even happen? Commented Feb 24 at 4:47
  • 2
    @Slav: Yes, if you require each request to include custom HTTP headers, this is sufficient CSRF protection (assuming you don't have an ultra-lax CORS policy where other sites can set this header). Commented Feb 24 at 10:09

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.