2

I have created an ASP.NET Core 3.1 web application from VS 2022 using the default template and selected Microsoft Identity to use Azure AD authentication. The wizards generated the app registration in my Azure AD tenant, and everything looks good (reply URLs etc.)

When I run the newly generated web application from the debugger, I get prompted for my Azure AD credentials and then I'm redirected back to my application and get the following exception:

Exception: Unable to unprotect the message.State.

Unknown location
Exception: An error was encountered while handling the remote login.

Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()

Note that I have not added or modified any code. It is 100% as generated by the VS2022 template.

OlavT
  • 2,496
  • 4
  • 31
  • 56
  • I googled your exception and I found [this issue](https://github.com/AzureAD/microsoft-identity-web/issues/1555), I want to check if you used IIS Express to debug your app? https://i.stack.imgur.com/raaKb.png – Tiny Wang Apr 27 '22 at 01:49
  • @TinyWang Yes, I used IIS Express to debug locally. – OlavT Apr 27 '22 at 04:59
  • Thank you for your confirm sir. And on this case, if you want to figure out why using the default temple from VS 2022 will cause an issue, I have no other ideas, but if you only want to find a way to integrate azure ad into your MVC project to make users sign in, you can refer to my [this answer](https://stackoverflow.com/a/71963533/15581227), it contains code and configurations. – Tiny Wang Apr 27 '22 at 05:43

4 Answers4

8
  • This error "Exception: Unable to unprotect the message.State", usually occurs when you have multiple OIDC middleware. Please check and see if you need to set a unique callback path for each OIDC if you have multiple.
  • Name the authentication schemes for each OIDC provider like oidc-demo and oidc-master.
  • The state parameter is not being decrypted properly when it is returned maybe because one of your OIDC providers is trying to decrypt/unprotect the message state of the other one.
  • Try to add the data protection provider as per the github discussion.

Please check the below links for data protection:

Configure ASP.NET Core Data Protection | Microsoft Docs

c# - Azure AD Authentication in Kubernetes Unable to unprotect the message.State - Stack Overflow

  • Make sure redirectURI should be an action method in your own controller like below:

            https://localhost:44381/test/callback 
    
  • Check UnProtect method in OnMessageReceived to decrypt as discussed in this link.

For more in detail, please refer below link:

asp.net core - Error code 500 "Unable to unprotect the message.State" when redirecting to Client in IdentityServer4 - Stack Overflow.

Rukmini
  • 6,015
  • 2
  • 4
  • 14
  • This project was generated by selecting to create new ASP.NET Core 3.1 Web Application from VS 2022, then selecting "Microsoft identity platform" in the wizard. The next was basically just to click Create and then configuring the "Microsoft identity platform" in the Connected services dialog. I have not modified a single line of code from what's being generated by default. I have no multiple OIDC middleware. – OlavT Apr 26 '22 at 14:13
  • 1
    I had to update CallbackPath from `/signin-oidc` to `/signin-oidc-azure-ad` (or whatever you want to) and then also in Azure -> Azure AD -> App registrations -> My App -> Authentication -> Web - Redirect URLs update/add url to `https://localhost:12345/signin-oidc-azure-ad` – Vaclav Elias Oct 14 '22 at 13:04
  • Genius. Solved it for me. Why isn't the default callback set to `/signin-` then to avoid having multiple (different) oidc implementations target the same url and confusing each other? – Jules Dec 28 '22 at 08:32
6

I was having the same problem with the vanilla ASP.NET 6 template with Azure AD auth, except that everything worked fine locally, but I received the Unable to unprotect the message.State. error when I deployed it to our kubernetes cluster.

For me, the issue was that the application was deployed to more than one instance behind the load balancer, so that caused the issues. I came across this issue on GitHub, which pointed me to this article that describes the problem and solution.

Solution 1

The article recommended utilizing a centralize data store that can be shared by all running instances to hold the auth keys, and set it up with code similar to this.

services.AddDataProtection()
    .SetApplicationName("MyApp")
    .SetDefaultKeyLifetime(TimeSpan.FromDays(30))
    .PersistKeysToAzureBlobStorage(new Uri("https://mystore.blob.core.windows.net/keyrings/master.xml"), new DefaultAzureCredential())
    .ProtectKeysWithAzureKeyVault(new Uri("https://myvault.vault.azure.net/keys/MasterEncryptionKey"), new DefaultAzureCredential());

Solution 2

My web app didn't utilize a database and I didn't want to introduce one just for auth, so instead I configured our ingress to use cookie persistence. This means that when a request is made, the response contains a cookie that the client will store and include on future requests. The cookie tells the ingress which instance to direct the request to, ensuring that requests from a specific client always end up hitting the same instance.

This may not be ideal in all scenarios, as it can prevent the load balancer from performing equal distribution of requests across all instances. That tradeoff was fine in my scenario though, as it's not a high volume service, and this is the solution I ended up using.

Here's an example of the nginx ingress yaml annotations I added:

  ingress:
    enabled: true
    annotations:
      nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
      nginx.ingress.kubernetes.io/affinity: cookie
      nginx.ingress.kubernetes.io/session-cookie-name: my-service-name
      nginx.ingress.kubernetes.io/session-cookie-hash: sha1
      nginx.ingress.kubernetes.io/session-cookie-max-age: "1800"

You can configure a similar cookie affinity rule in other products, like F5 load balancers, Azure App Gateways, etc.

Solution 3

The last option is to host only a single instance of your service, in which case all auth callbacks will hit that single service. This isn't ideal though, as it means you can't scale your web app for high availability.


How this relates to the original poster's issue of getting this error on their localhost while debugging, I'm not certain. Perhaps they have more than once instance running on their local machine, or a reverse proxy, or interceptor (like Fiddler) running on their localhost that is causing the problem? Either way, I thought I'd share my solutions for others that stumble across this question when searching for the error message.

deadlydog
  • 22,611
  • 14
  • 112
  • 118
0

In my case, the issue was when I have more than one running instance of the application. The problem is that when the callback is triggered you don't know to which instance of the application you will be returned. The fix is to use data protection with distributed cache like Redis.

0

Please consider this as an extension to the answers provided above.
We experienced the same error due to the simultaneous generation of the data protection key by 2 app instances behind a load balancer when the existing data protection key was near expiration. 2 different keys were generated and used during the next 24 hours causing the app erroring

Here is more information related to such race conditions:
What is the strategy of data-protection key rotation with multiple pods?
https://github.com/dotnet/aspnetcore/issues/28475

Additionally, there are such open issues on GitHub devoted to this error:
https://github.com/dotnet/aspnetcore/issues/3514
https://github.com/dotnet/aspnetcore/issues/36157

OL.
  • 201
  • 3
  • 13