Closed LeaFrock closed 2 years ago
Yes, this is a DataProtection configuration issue. Are you running this in IIS or on HttpListener? In IIS you only need to set up a shared machine key. Outside of IIS the DataProtection configuration is a bit more manual.
Thanks for you reply! @Tratcher
It does not work.
Maybe there're more problems. For example, I notice that, RememberNonce will store nonce cookie into memory of app-1, and when RetrieveNonce reads nonce from CookieManager
of app-2, will it always return null and then make the validation fail?
On our test environment the webform app is deployed as singleton and everything runs well. Now it becomes a disaster after we push the new version to online, and we have to roll back and solve the problems. I'm still not sure about the reason and looking for help about locating the point.
Maybe there're more problems. For example, I notice that, RememberNonce will store nonce cookie into memory of app-1, and when RetrieveNonce reads nonce from
CookieManager
of app-2, will it always return null and then make the validation fail?
Cookies are only stored on the client, not on the server.
Are you able to set up a repro and attach a debugger? The symbols should be available.
Yep. After two days working on this issue, I find that it's the problem of load balance system from our cloud service provider.
The nonce cookie is set by CookieManager.AppendResponseCookie
. Without using LB, I can see Set-Cookie: OpenIdConnect.nonce.xxxxx
in the http response headers; but as long as LB is used, the Set-Cookie
is gone. Because the problem of LB can't be solved immediately, finally I have to set RequireNonce
to false
.
However, I find another interesting question. If RequireNonce
is false
, we get another exception: IX21329: RequireState xxxx
.
At the very beginning, we make a Nuget package for our own productions for easier usage,
public class MyAuthenticationOptions : OpenIdConnectAuthenticationOptions
{
public MyAuthenticationOptions() : base(OpenIdConnectAuthenticationDefaults.AuthenticationType)
{
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
ResponseType = OpenIdConnectResponseType.Code;
RedeemCode = true;
Scope = "xxx";
ResponseMode = OpenIdConnectResponseMode.Query;
SaveTokens = true;
BackchannelTimeout = TimeSpan.FromSeconds(5.0);
PostLogoutRedirectUri = "/";
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = GetUserInformationAsync,
RedirectToIdentityProvider = SetIdTokenHint
};
}
}
with an extension method,
public static IAppBuilder UseMyAuthentication(this IAppBuilder app, Action<MyAuthenticationOptions> configuration)
{
var options = new MyAuthenticationOptions()
{
Authority = "mydomain",
};
configuration?.Invoke(options);
// other codes
app.UseOpenIdConnectAuthentication(options);
return app;
}
I notice that the base constructor has already initialized ProtocolValidator
, and the ValidateState
in OpenIdConnectProtocolValidator
should always be skipped when RequireStateValidation
is false
.
ProtocolValidator = new OpenIdConnectProtocolValidator()
{
RequireStateValidation = false,
NonceLifetime = TimeSpan.FromMinutes(15)
};
But now, I have to copy these codes to MyAuthenticationOptions
, or to UseMyAuthentication
, and then the RequireState
exceptions are gone.
app.UseMyAuthentication(config =>
{
// other codes
config.ProtocolValidator = new OpenIdConnectProtocolValidator()
{
RequireStateValidation = false,
NonceLifetime = TimeSpan.FromMinutes(15)
};
})
I'm still not sure why the ProtocolValidator
is not intialized as expected without extra codes above. But now it works well online, and I can take a good sleep at least 😞 .
The nonce cookie is set by CookieManager.AppendResponseCookie. Without using LB, I can see Set-Cookie: OpenIdConnect.nonce.xxxxx in the http response headers; but as long as LB is used, the Set-Cookie is gone.
New progress. The nonce cookie is set by the codes,
Options.CookieManager.AppendResponseCookie(
Context,
GetNonceKey(nonce),
Convert.ToBase64String(Encoding.UTF8.GetBytes(Options.StateDataFormat.Protect(properties))),
new CookieOptions
{
SameSite = SameSiteMode.None,
HttpOnly = true,
Secure = Request.IsSecure,
Expires = DateTime.UtcNow + Options.ProtocolValidator.NonceLifetime
});
Notice the line, Secure = Request.IsSecure
. Our load balance system will hold all https related works, and then just forward them to the app instances in http. Therefore, the IsSecure
is false
, and finally set a cookie like below,
A valid cookie with SameSite=None
must have Secure
too, otherwise the browser will abandon it. So it seems to be the real problem. If so, is there any solution or workground?
p.s. Why not just set Secure=true
as same as HttpOnly
?
p.s.2 I also check the codes of ASP.NET Core here, it has CookieBuilder
for nonce which is a pretty solution for my problem. But for Owin, there is not the same property 😢 .
The recomendation is to update the request fields to match the public endpoint (scheme, host, port) so that when link and cookies are generated they use the correct value. See similar asp.net core samples: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-6.0#scenarios-and-use-cases
Thanks for your answer. And also sorry that, I just find the similar issue #332, #352 about nonce cookie problem 😿 .
Recently we're upgrading the auth way of a webform app to OpenIdConnect. We've imported
Microsoft.Owin.Security.OpenIdConnect
and it works well if the app is deployed as singleton. But when we deploy more instances and add load balance, the apps throw exceptions related torequire nonce
orrequire state
.I find that it seems to be the problem of default
DataProtector
Owin uses. If a challenge is requested by app-1, and the user logins successfully in Identity Server, then the server redirects the browser toxxx/signin-oidc?code=xxx&state=xxx&...
. Due to load balance, the callback is sent to app-2, and app-2 cannot unprotectstate
and finally stops the next step.Would anyone like to give me a solution, please?
p.s. I find this issue #435, but in fact I'm not going to share something with a .NET Core app. It will import more Nuget packages which are not updated after 2018. I just want to make sure the instances of same app can protect/unprotect each other.