In a stateless design most common approach for CSRF protection is double submit cookie. Even though the server does not have a state or session, a security session is present on the client side. It is very important to implement double submit cookie in a way that it is tied to this security session and not to do it naively. Please see this paper for more details.
One approach to achieve this is to include CSRF token inside authentication cookie and make authentication cookie httpOnly. This way an attacker on a compromised subdomain will be forced to overwrite both authentication cookie and CSRF token which he can't do since he can't fake authentication cookies. Even if he has a valid authentication cookie (his own for example), this will change authenticated user and thus defeat the purpose of CSRF in the first place. Also, overwriting the cookie is not enough to defeat CSRF since client application is not reading CSRF token from the cookie itself (it is httpOnly) but it is given a CSRF token on a successful authentication.
Steps needed to implement this approach can be found here. Even though the answer on that link is about JWT, the same approach is valid for any token format you use for authentication.
In stateless microservice architecture the best approach is to handle CSRF on the same place where you handle authentication be it on gateway or some other service behind it since you will need to handle authentication on every request and the CSRF token is (if you implement my advice above) tied to the authentication. Any other option option will complicate things further and possibly break CSRF protection altogether. For example:
- You are processing authentication on a gateway or other service but can't change the way authentication is performed and thus can't add CSRF protection at that place. In this case you will need to extract CSRF token out of the auth token and forward it to target service along with CSRF header for processing. This implies you will need to do CSRF protection in each and every service which is not a good thing. You can't guarantee each service will perform this check. Also, you will need to worry about inter service communication. Disabling CSRF protection with a presence of a certain header is possible but might be a security risk in itself.
- You are not performing authentication on a gateway or you don't have a gateway, client credentials (authentication token/cookie) are forwarded to every service in chain of request and each service needs to handle it's own authentication/CSRF protection. Same as above, you can't guarantee each service will do it but the approach for handling CSRF is outlined above.
One other approach to handle CSRF is not to have cookies for authentication tokens and instead use headers for passing authentication tokens around. In case of an XSS vulnerability, this will make it easier for authentication token to be stolen though. An article about this dilemma can be found here.