Using ASP.NET Core 3.1 I am creating a new API for a single page application that is built separately and served as static files.
I thought that I would be served well with .AddControllers() as I'm just doing an API, no views.
Naturally I wanted to add CSRF protection. I started with .AddAntiforgery() and in the controllers options:
options.Filters.Add<AutoValidateAntiforgeryTokenAttribute>();
But that failed, because the attributes requires a service AutoValidateAntiforgeryTokenAuthorizationFilter, which wasn't registered and is an internal type.
This is where things started going bad.
First, I would suggest improving the docs and error messages: the antiforgery docs should clearly say what are the precise requirements for making it work. Ideally when an internal service is not found, the error message should point out what configuration is missing. Even if it's just a generic link to a page where all internal services are listed with their matching configuration call(s).
Googling did not bring up anything useful so I started looking at the source code on Github.
It seems that this internal type is only added to services if you use AddMvcCore and then AddViews (I found two other methods but they're even less appropriate).
Unless I missed something (please let me know), it feels wrong that to use a controller attribute you have to opt into the full views/templating stuff.
Antiforgery is a security practice that every web api should include, it should be usable with .AddControllers or even .AddMvcCore without views.
The reasoning is that anti forgery isn鈥檛 required if you aren鈥檛 doing form posts from a browser client (which your rarely are when doing APIs). What鈥檚 the scenario? What type of API are you building that you need anti forgery without a browser client and views?
It's a REST-like API that is the back-end of a SPA application (built with Vue) that performs GET, POST and all kind of calls using fetch.
From a security perspective, I don't understand your comment. What _I_ do doesn't matter much. It's what _an attacker_ could do from another site.
My GET don't modify state (although that could be someone else's case) but I have POST operations exposed so I'd better have a way to prevent CSRF, haven't I?
If you rely on the format of the payload to prevent the attack:
Also: security in depth, I'm not trying to be smart with how I prevent CSRF.
EDIT: I shall add that ASP.NET antiforgery doc specifically suggests sending the csrf token to SPA clients through a cookie and indicates that Angular will automatically reflect the value in a header. This sounds like implicit support for API without forms scenarios, which doesn't work end-to-end as you can't validate the token.
We鈥檝e been pushing users towards JWT tokens for APIs and not cookies (if you鈥檙e using cookies with APIs the it makes sense wanting anti forgery to be enabled).
How is does the client get the anti forgery token? Are you custom rendering it and parsing it out of the body before sending the request?
cc @javiercn @blowdart
I guess it also makes sense if using automatic NTLM? (I'm also using Cookies on other apps)
There are plenty of ways to send the token and I've used several in my apps:
You should be able to register antiforgery on its own and to use the antiforgery token to generate the state required for this to work either on your initial page render or later.
I don't believe that we have a specific sample of cookies + antiforgery because it is something we discourage in favor of using bearer tokens. Specially for SPA scenarios.
The antiforgery token has two pieces, the request token and the cookie token. The request token can be sent inside a response header or inside a JS accessible cookie or transferred to the client through any other mechanism.
The filters for antiforgery are defined within the ViewFeatures assembly, but they should be usable outside of them.
The filters for antiforgery are defined within the ViewFeatures assembly, but they should be usable outside of them.
This is the issue.
Filters are internals and only added to services when you add views.
If I missed something, can you please provide a small ConfigureServices sample demonstrating antiforgery filters registration with AddControllers?
I believe this is because we separated things into AddMvcCore and AddMvc. I don't think we consider antiforgery a piece of AddMvcCore and that's not something we are likely going to change, since using antiforgery with APIs is something we discourage.
What you can do is, either write your own antiforgery filter to apply to your api controllers:
public class ApiAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{
private readonly IAntiforgery _antiforgery;
public ApiAntiforgeryTokenAuthorizationFilter(IAntiforgery antiforgery) => _antiforgery = antiforgery;
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (!context.IsEffectivePolicy<IAntiforgeryPolicy>(this))
{
return;
}
try
{
await _antiforgery.ValidateRequestAsync(context.HttpContext);
}
catch (AntiforgeryValidationException exception)
{
context.Result = new AntiforgeryValidationFailedResult();
}
}
}
or use the IAntiforgery service within your method body to validate the token.
@javiercn
I don't think we consider antiforgery a piece of AddMvcCore
Actually you can add Antiforgery to AddMvcCore if you chain AddViews to it. Not ideal but possible.
AddMvcCore and AddMvc are not the only ways. I'm specifically mentionning AddControllers (new in 3.0) in which case adding the antiforgery filters is impossible.
since using antiforgery with APIs is something we discourage.
Fair enough but there's a difference between discouraging and not supporting.
We have security practices that have been validated by external reviewers and I can't change them in a blink, even if a bearer token would be better. I'm sure I'm not the only enterprise developer still using CSRF tokens.
Out of curiosity: my api uses automatic NTLM authentication. I have no need for a JWT token for auth, would you say CSRF is discouraged in this case? I'm curious about what you'd say is a security best practice here.
What you can do is, either write your own antiforgery filter
I can do this but wow that's really disappointing. Everything is avail. in asp.net but you just don't expose it.
Can't you add a new API .AddAntiforgery that can be chained to AddControllers? How does this non-view filter not make sense here?
Or can't you register the antiforgery filters with .AddControllers? It's a controller thing, heck you do register them automatically with .AddMvcCore().AddViews().
Or make those filters public.
or use the IAntiforgery service within your method body to validate the token.
This is not a prototype but an enterprise app. Hundreds of methods, not gonna happen.
In any case, you may want to review your docs and API a bit.
I read the docs, then put [ValidateAntiforgeryToken] on my controller... and boom I get a hard to debug exception referencing an internal type. Makes no sense and bad experience. Googling didnt' help.
As for the docs: please read the 3.1 docs Javascript, Ajax and SPAs in Anti-request forgery. It's quite long and gives various options to integrate CSRF with your SPA:
@davidfowl @javiercn @blowdart
Would you not consider pushing towards cookies again with strict same domain protection? Renewing / refreshing tokens in the browser seems like a problem in the near future and using a backend for security would be the safest way. Using refresh tokens seems a bit dangerous at the moment, especially if no support exists for the revocation endpoint and most secure token server implementations don鈥檛 have the protections to make refresh tokens in the browser less dangerous, or the guidelines for using refresh tokens in browsers are still in progress. With YARP coming, cookies would make even more sense.
Greetings Damien
_Would you not consider pushing towards cookies again with strict same domain protection_
Maybe, but the problem is all those older machines, with older browsers which still don't support any version of same site, let alone the new spec. Like IE, older versions of Safari on iPads and iPhones which will never get updated, or older versions of Android.
We cannot just limit ourselves to supporting the latest and greatest in this regard unfortunately, and there's no easy way to test browser support as the user agent sniffing we have to implement for SameSite shows.
@blowdart Would the anti forgery token not be enough for the old browsers? The modern browser have both, this and same site. The question is "Which is less evil, refresh tokens in the browser, or only anti-forgery tokens for old browsers" Then the user has a choice. But I understand supporting only tokens, supporting 2 security architechtures is double the effort.
I agree with @jods4 that having someway of registering or chaining .AddAntiForgery onto .AddControllers would be a great solution and allow developers to add the functionality if their situation called for it.
In our case we are going an Angular app with cookies, too large to convert to using JWT tokens currently, and everything works great, we use services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); and it checks and validates the antiforgery token as needed using all the built-in attributes. We pass down the token in the initial call for SPA html page.
The only problem is the only way to get it to work now is to call .AddControllersWithViews instead of .AddControllers. If there was a way to chain the antiforgery internal functionality onto this, or even an example of how to register the needed pieces ourselves that would be great and solve our situation.
We鈥檝e been pushing users towards JWT tokens for APIs and not cookies (if you鈥檙e using cookies with APIs the it makes sense wanting anti forgery to be enabled).
How is does the client get the anti forgery token? Are you custom rendering it and parsing it out of the body before sending the request?
cc @javiercn @blowdart
I believe this is because we separated things into AddMvcCore and AddMvc. I don't think we consider antiforgery a piece of AddMvcCore and that's not something we are likely going to change, since using antiforgery with APIs is something we discourage.
This seems like a very heavy handed tactic for a framework that is supposed to be customizable for your unique situation. Believe it or not Bearer tokens _are not_ the safest option in _all_ cases and there are legitimate reasons to want to authenticate an API with cookies (http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/) . In our case I'm running an ASP.NET Core 3.1 API that I am authenticating with cookies and I'm running into the same issue trying to get anti-forgery tokens working without having to add the entire MVC framework just to support this.
Trying to force bearer tokens on everybody is not the solution. We should be able to use the anti-forgery token code without needing to add all of MVC (which BTW security wise is a bad idea to have to add a lot of unneeded code that increases your attack surface).
In regard to how the client receives the token, you can send it back with a login action (https://odetocode.com/blogs/scott/archive/2017/02/06/anti-forgery-tokens-and-asp-net-core-apis.aspx).
I agree with @jods4 that having someway of registering or chaining
.AddAntiForgeryonto.AddControllerswould be a great solution and allow developers to add the functionality if their situation called for it.In our case we are going an Angular app with cookies, too large to convert to using JWT tokens currently, and everything works great, we use
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");and it checks and validates the antiforgery token as needed using all the built-in attributes. We pass down the token in the initial call for SPA html page.The only problem is the only way to get it to work now is to call
.AddControllersWithViewsinstead of.AddControllers. If there was a way to chain the antiforgery internal functionality onto this, or even an example of how to register the needed pieces ourselves that would be great and solve our situation.
Thank you @mcardoalm , using AddControllersWithViews instead of just AddControllers fixed the error.
On that note, however, I see that it is automatically sending back the value in a cookie as well... does anyone know of a way to disable this cookie so that the server does not send it with each response?
Most helpful comment
@javiercn
Actually you can add Antiforgery to
AddMvcCoreif you chainAddViewsto it. Not ideal but possible.AddMvcCoreandAddMvcare not the only ways. I'm specifically mentionningAddControllers(new in 3.0) in which case adding the antiforgery filters is impossible.Fair enough but there's a difference between discouraging and not supporting.
We have security practices that have been validated by external reviewers and I can't change them in a blink, even if a bearer token would be better. I'm sure I'm not the only enterprise developer still using CSRF tokens.
Out of curiosity: my api uses automatic NTLM authentication. I have no need for a JWT token for auth, would you say CSRF is discouraged in this case? I'm curious about what you'd say is a security best practice here.
I can do this but wow that's really disappointing. Everything is avail. in asp.net but you just don't expose it.
Can't you add a new API
.AddAntiforgerythat can be chained toAddControllers? How does this non-view filter not make sense here?Or can't you register the antiforgery filters with
.AddControllers? It's a controller thing, heck you do register them automatically with.AddMvcCore().AddViews().Or make those filters public.
This is not a prototype but an enterprise app. Hundreds of methods, not gonna happen.
In any case, you may want to review your docs and API a bit.
I read the docs, then put
[ValidateAntiforgeryToken]on my controller... and boom I get a hard to debug exception referencing an internal type. Makes no sense and bad experience. Googling didnt' help.As for the docs: please read the 3.1 docs Javascript, Ajax and SPAs in Anti-request forgery. It's quite long and gives various options to integrate CSRF with your SPA: