The antiforgery token could not be decrypted — Running ASP.NET core on Google Cloud.

I typed in my name, clicked Submit, and saw a custom greeting:

It worked great on my machine, but when I deployed it to Google App Engine, I saw this error:

System.Security.Cryptography.CryptographicException: The key {12471710-d939-4a6f-a408-54eb7efcaf33} was not found in the key ring.
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect
at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize

I’m not even using encryption. What is going on?

The strongest hint is at the bottom of the stack trace above: DefaultAntiforgeryTokenSerializer.Deserialize.

Antiforgery tokens are a defense against Cross-Site Request Forgery (CSRF) attacks. Attackers use CSRF to trick users into performing actions that benefit the attackers and cost the user. Wikipedia.org and owasp.org do a great job of explaining the attack in detail.

The good news is that ASP.NET core has a built-in countermeasure for CSRF attacks. It’s fully documented at docs.microsoft.com. I always use this countermeasure by adding the ValidateAntiForgeryToken property to controller methods that receive form POSTs:

The bad news (for running the app on Google App Engine) is that the code behind ValidateAntiForgeryToken uses encryption, and by default it stores encryption keys on the local web server. When I had only one web server running on my desktop, it worked beautifully. But, when I had multiple web servers running on App Engine, each web server had its own keys. I saw the CryptographicException because my GET request to fetch the form and my POST request to submit the form were served by different web servers, and the web servers had different keys.

In an earlier post, I described a hand-coded solution to this issue. It worked reliably, but deviated from the usual pattern documented at docs.microsoft.com. Here’s the usual pattern:

Announcing Two New Libraries from Google

Google just released alpha versions of two new libraries that solve this problem, and conform to the usual pattern shown above. Google.Cloud.AspNetCore.DataProtection.Storage persists keys to Google Cloud Storage and Google.Cloud.AspNetCore.DataProtection.Kms protects the keys with Google Cloud Key Management Service. Here’s how to use them:

These new libraries are designed to save developers hours of time solving this problem quickly and consistently no matter where your application runs, even outside Google Cloud! For example, I’ve used Google.Cloud.AspNetCore.DataProtection.Kms to protect keys stored on my local machine.

Try the code!

We want to hear about your experience using these new libraries, so please give them a try and report issues on the github.com repo.

The sample application includes a setup script, Set-Up.ps1, which will create the Google Cloud Storage bucket, create the KMS master key, and set the minimal necessary permissions for App Engine to encrypt and decrypt the keys.