I am using both Google and Facebook authentication mechanisms in my .NET 7.0 app and they both work fine locally. When deployed in my DEV environment I am getting exceptions when coming back from Google/Facebook. So the challenge works correctly, I am able to authenticate at their side but the callback fails, saying:
Exception: The oauth state was missing or invalid.
Unknown location
I am being redirected to https://my.website.com/signin-google
with the state in a querystring parameter. This is the expected behavior as I did not configure an explicit callback path and by default it's set to signin-google
and signin-facebook
. But somehow it seems like the RemoteAuthenticationHandler
does not think this matches the callback path so it's not handling the request? Or would the issue be in the OAuthHandler.HandleRemoteAuthenticateAsync
? Maybe when unprotecting the state data? But then why would this work locally?
My setup:
services.AddAuthentication()
.AddGoogle("google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = AppSettings.Instance.GoogleClientId;
options.ClientSecret = AppSettings.Instance.GoogleClientSecret;
})
.AddFacebook("facebook", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.AppId = AppSettings.Instance.FacebookAppId;
options.AppSecret = AppSettings.Instance.FacebookAppSecret;
});
Edit: Could this possibly be linked to the fact I am using 2 servers in my DEV environment and that it uses something machine-related to unprotect the state so it does not work when I land on the other machine?
CodePudding user response:
It turns out this was due to the fact I am now deploying the application on multiple distributed servers, accessed via a load balancer. This article here helped me to understand how data protection works in ASP.NET Core.
By default key-rotation system is used and the keys are generated by the machine and persisted to %LOCALAPPDATA%\ASP.NET\DataProtection-Keys
. So of course this does not work anymore when you scale up your applications.
So instead I decided to store the data protection keys in a shared database so all machines will use the same key:
Add reference to Nuget
Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
.Create a DbContext that inherits from
IDataProtectionKeyContext
Register your DbContext
Setup your data protection to use the DbContext as:
builder.Services.AddDataProtection() .PersistKeysToDbContext<DataProtectionKeyContext>();