I have deployed an asp.net core app on some load balanced linux servers. I getting an error when POSTing a form to a route due to a failing ValidateAntiForgeryToken
attribute (if a POST does not go back to the same machine as the one that generated my form).
With Windows and .Net classic I know to match MachineKey
attributes in my web.config
or machine.config
files.
So, how do I achieve the same on linux hosts and allow a token from one server to be validated on another?
The common “possible solutions” to anti-forgery token/cookie related issues are disabling output caching and enabling heuristic checks.
The basic purpose of ValidateAntiForgeryToken attribute is to prevent cross-site request forgery attacks. A cross-site request forgery is an attack in which a harmful script element, malicious command, or code is sent from the browser of a trusted user.
HttpPost: The HttpPost attribute which signifies that the method will accept Http Post requests. ValidateAntiForgeryToken: The ValidateAntiForgeryToken attribute is used to prevent cross-site request forgery attacks.
AntiForgeryToken() Generates a hidden form field (anti-forgery token) that is validated when the form is submitted.
So Antiforgery support is added automatically when you call services.addMvc()
. You can alter the basic configuration by calling services.AddAntiforgery(opts => "your options")
.
Under the hood, the token is protected by ASP.Net Core Data Protection library (github repo here). By default I think this is in memory, so keys generated, and then used for token protection, are not shared on a mulitple / cloud server scenario.
Solution
So to share antiforgery tokens, you can set up the Data Protection service with a shared location. The default ones that come with the data protection library are:
//File system
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\some\shared\directory\"));
//Registry
services.AddDataProtection()
.PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Sample\keys"));
Then there are a couple of defaults for better shared storage included:
//redis
var redis = ConnectionMultiplexer.Connect("my-redis-url");
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys");
//Azure
services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri("blob-URI"));
I have also found (and used!) an option for AWS S3 storage from github thanks to a github user named CL0SeY.
Update: Amazon released their own implementation for using AWS SSM parameter store as well. Github repo here. For testing
By default, tokens have a lifetime of 90 days. This can be set when you add the service. So one way to get a simple solution for testing is to generate a key to filesystem with a long lifetime, then deploy that token to a known location on your servers. Then set up data protection from that location, but tell it to never generate new keys:
//generate a test key with this in a test app or whatever:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp\"))
.SetDefaultKeyLifetime(TimeSpan.MaxValue);
// then use that key in your app:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\some\allowed\directory"))
.DisableAutomaticKeyGeneration();
On linux
All of this should work on when hosted on linux with the only caveat being that you shouldn't reference windows drives or locations (duh). I am not 100% sure what would happen if you tried the registry option either...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With