identity server load balancing logout issue - docker

i am working on identity server 4 with .net core micro service architecture, i have followed this reference application please click Here.i have also used docker container to deploy distributed approach application.its working fine when it is in development i.e local environment.
but on production i am using Load balancing on identity server, because of load balancing i am facing log out issue.
Ex . i have created 3 instances of Identity server for production purposes i.e A,B and C, based on user load it will automatically switch between instances.Now the problem is when user logged in A instances after few seconds it automatically requests to B or C instances because of load balancing, so problem is user logged in A instance and current request is handled by B or C instance so how server will know that user is logged in or not,that's why its logging out me and redirects to log in screen.
EDIT :
Please check with Identity server configuration and operational store with signin certificate

I believe the issue you have is due to the data protection in asp.net core, each container will be using different keys to encrypt / decrypt data. To verify just run one instance of the container, if this resolves your problem then look at : https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/
public class XmlToDbRepository : IXmlRepository
{
private readonly IPersistKeyDb _persistKeyDb;
public XmlToDbRepository(IPersistKeyDb persistKeyDb)
{
_persistKeyDb = persistKeyDb;
}
public IReadOnlyCollection<XElement> GetAllElements()
{
return _persistKeyDb.GetAll().Select(i => XElement.Parse(i.Key)).ToList().AsReadOnly();
}
public void StoreElement(XElement element, string friendlyName)
{
_persistKeyDb.Store(friendlyName,element.ToString(SaveOptions.None));
}
}
I think this is the one you'll be interested in.

Related

How can I know if my app is running under Kestrel or HTTP.sys?

What's the best way to know if my app is running under Kestrel or HTTP.sys. All that I have found so far is to check for "Kestrel" in one of the HttpConext property class names.
Within an MVC controller I can do something like this:
Boolean IsKestrel = HttpContext.Features.GetType().ToString().Contains("Kestrel");
i.e. check this:
Features = {Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection<Microsoft.AspNetCore.Hosting.HostingApplication.Context>}
When using HTTP.sys I only get:
Features = {Microsoft.AspNetCore.Http.Features.FeatureCollection}
(Is "Http" here enough to know that this is HTTP.sys?)
There must be a better way. Is there an obvious property somewhere that contains the name of the host being used?
A broader question might be, how do I know what the builder pattern built?
Update
Found something better, but still looking for a Property that has the server name or type.
In an MVC controller:
var isKestrel = HttpContext.Request.Headers.GetType().ToString().Contains(".Kestrel.");
var isHTTPsys = HttpContext.Request.Headers.GetType().ToString().Contains(".HttpSys.");
At the operating system level, netsh http show servicestate will list all active URLs listening via HTTP.SYS.
From code you can locate an instance of Microsoft.AspNetCore.Hosting.Server.IServer and check what its implementation is, in netcore 6:
Kestrel => Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl
IIS ==> Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer
HTTP.SYS => Microsoft.AspNetCore.Server.HttpSys.MessagePump
This relies on implementation details (so can break), also other extensions can change these e.g. CoreWcf creates CoreWCF.Configuration.WrappingIServer that wraps one of the above implementations.
you can use System.Diagnostics.Process.GetCurrentProcess().ProcessName
I am not sure whether you want to check this information using the code only or you are just looking for a way to know on which web server your app is running.
In my search result, I found that we could set the ports for a specific web server. When the application will run on that specific web server then it will use that pre-configured port. I am assuming your app also has a similar configuration. You could set the different ports for Kestrel, Http.sys, or IIS. By checking the port number you could say that on which web server your site is running.
You could try to go to the launchSettings.json file in your project where you could configure ports for IIS and Kestral.
Helpful References:
Kestrel Web Server in ASP.NET Core
Understand HTTP.sys Web Server In ASP.NET Core
Hello this is a good question, you question is asking how to find out from inside the code and not from a console.
OOB I did not find anything. So, I had to get very creative to figure this out, sorry for the typo's its brand new stuff...
Option 1:
Since the Kestrel section & endpoints are inside the appsettings.json I used that to find out if its hosted by Kestrel!
//Please create a static class to hold the config.
public static class MyStartupIsItKestrelConfiguration
{
public static IConfiguration Configuration;
public bool static IsKestrel()
{
//check your section kestrel??
var kestrel = configuration.GetSection("Kestrel");
// now check kestrel section or any other section
// see picture for kestrel endpoint in app setting sbelow
return true;
}
}
Now you can access it anywhere and see if you used Kestrel
//Now add it/save it in your startup and access later
public Startup(IConfiguration configuration)
{
Configuration = configuration;
MyStartupIsItKestrelConfiguration.Configuration = configuration;
}
Once you have this
//you can use it in ** YOUR CONTROLLER
MyStartupIsItKestrelConfiguration.IsKestrel();
Option 2:
Please check this public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; }
You can get the features public TFeature? Get<TFeature> (); as a Key Value Pair - and then check the feature for e.g. KESTREL DOES NOT ALLOW PORT SHARING
they split the features namespace in .net core 6 there are breaking changes
You should use the features collection

HttpContext.Current.Session gets cleared on routing in Azure Server

public static WebUser LoggedUser
{
get
{
WebUser sessionValue = null;
if (HttpContext.Current.Session["LoggedUser"] != null)
{
sessionValue = (WebUser)HttpContext.Current.Session["LoggedUser"];
}
return sessionValue;
}
set
{
HttpContext.Current.Session["LoggedUser"] = value;
}
}
HttpContext.Current.Session gets cleared on routing in Azure Server, the same works fine in IIS server but why not in Azure??
Please help me in persisting session value
HttpContext.Current.Session gets cleared on routing in Azure Server, the same works fine in IIS server but why not in Azure??
I assume that you use the multiple instances Azure WebApp. If it is that case, we could get the answer from the azure official document. Please have a try use external session state provider (either the Redis Cache Service or a SQL Server session state provider).
If your ASP.NET web app uses session state, you will need to configure an external session state provider (either the Redis Cache Service or a SQL Server session state provider). If you use session state, and don't use an external provider, you will be limited to one instance of your web app.

OWIN asynchronous startup (using Hangfire)

I am using Hangfire with SQL Storage on a remote SQL server and running it alongside my existing MVC site. My startup class is very simple:
public void Configuration(IAppBuilder app)
{
app.UseHangfire(config =>
{
config.UseSqlServerStorage("MY_CONNECTION_STRING");
config.UseServer();
});
}
The problem is that any delay in connecting to the remote server delays my MVC site from spinning up. Is there a way to start OWIN asynchronously so that the project is able to respond to requests regardless of what happens during the OWIN startup, including fatal errors?
Hangfire initialization logic is performed in a dedicated thread to decrease the start-up time of your application. So, UseServer method creates a new thread only, without any additional logic.
UseSqlServerStorage method connects to your database to check your current schema to run automatic migrations if necessary (one simple query to the Hangfire.Schema table). This is the default behavior, however you are able to disable it:
var options = new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = false
};
var storage = new SqlServerStorage("<name or connection string>", options);
After performing this step, Hangfire will not connect to your database at startup (and no other class will do it). But keep an eye on release notes, they will contain information about database storage changes.

The anti-forgery token could not be decrypted

I have a form:
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()...
and action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl, string City)
{
}
occasionally (once a week), I get the error:
The anti-forgery token could not be decrypted. If this application is
hosted by a Web Farm or cluster, ensure that all machines are running
the same version of ASP.NET Web Pages and that the configuration
specifies explicit encryption and validation keys. AutoGenerate cannot
be used in a cluster.
i try add to webconfig:
<machineKey validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps" />
but the error still appears occasionally
I noticed this error occurs, for example when a person came from one computer and then trying another computer
Or sometimes an auto value set with incorrect data type like bool to integer to the form field by any jQuery code please also check it.
I just received this error as well and, in my case, it was caused by the anti-forgery token being applied twice in the same form. The second instance was coming from a partial view so wasn't immediately obvious.
validationKey="AutoGenerate"
This tells ASP.NET to generate a new encryption key for use in encrypting things like authentication tickets and antiforgery tokens every time the application starts up. If you received a request that used a different key (prior to a restart for instance) to encrypt items of the request (e.g. authenication cookies) that this exception can occur.
If you move away from "AutoGenerate" and specify it (the encryption key) specifically, requests that depend on that key to be decrypted correctly and validation will work from app restart to restart. For example:
<machineKey
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7
AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B"
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
validation="SHA1"
decryption="AES"
/>
You can read to your heart's content at MSDN page: How To: Configure MachineKey in ASP.NET
Just generate <machineKey .../> tag from a link for your framework version and insert into <system.web><system.web/> in Web.config if it does not exist.
Hope this helps.
If you get here from google for your own developer machine showing this error, try to clear cookies in the browser. Clear Browser cookies worked for me.
in asp.net Core you should set Data Protection system.I test in Asp.Net Core 2.1 or higher.
there are multi way to do this and you can find more information at Configure Data Protection and Replace the ASP.NET machineKey in ASP.NET Core and key storage providers.
first way: Local file (easy implementation)
startup.cs content:
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// .... Add your services like :
// services.AddControllersWithViews();
// services.AddRazorPages();
// ----- finally Add this DataProtection -----
var keysFolder = Path.Combine(WebHostEnvironment.ContentRootPath, "temp-keys");
services.AddDataProtection()
.SetApplicationName("Your_Project_Name")
.PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}
}
second way: save to db
The Microsoft.AspNetCore.DataProtection.EntityFrameworkCore NuGet
package must be added to the project file
Add MyKeysConnection ConnectionString to your projects
ConnectionStrings in appsettings.json > ConnectionStrings >
MyKeysConnection.
Add MyKeysContext class to your project.
MyKeysContext.cs content:
public class MyKeysContext : DbContext, IDataProtectionKeyContext
{
// A recommended constructor overload when using EF Core
// with dependency injection.
public MyKeysContext(DbContextOptions<MyKeysContext> options)
: base(options) { }
// This maps to the table that stores keys.
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
}
startup.cs content:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// ----- Add this DataProtection -----
// Add a DbContext to store your Database Keys
services.AddDbContext<MyKeysContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyKeysConnection")));
// using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.PersistKeysToDbContext<MyKeysContext>();
// .... Add your services like :
// services.AddControllersWithViews();
// services.AddRazorPages();
}
}
If you use Kubernetes and have more than one pod for your app this will most likely cause the request validation to fail because the pod that generates the RequestValidationToken is not necessarily the pod that will validate the token when POSTing back to your application. The fix should be to configure your nginx-controller or whatever ingress resource you are using and tell it to load balance so that each client uses one pod for all communication.
Update: I managed to fix it by adding the following annotations to my ingress:
https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/
Name Description Values
nginx.ingress.kubernetes.io/affinity Sets the affinity type string (in NGINX only cookie is possible
nginx.ingress.kubernetes.io/session-cookie-name Name of the cookie that will be used string (default to INGRESSCOOKIE)
nginx.ingress.kubernetes.io/session-cookie-hash Type of hash that will be used in cookie value sha1/md5/index
I ran into this issue in an area of code where I had a view calling a partial view, however, instead of returning a partial view, I was returning a view.
I changed:
return View(index);
to
return PartialView(index);
in my control and that fixed my problem.
I got this error on .NET Core 2.1. I fixed it by adding the Data Protection service in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
....
}
you are calling more than one the #Html.AntiForgeryToken() in your view
I get this error when the page is old ('stale'). A refresh of the token via a page reload resolves my problem. There seems to be some timeout period.
I found a very interesting workaround for this problem, at least in my case. My view was dynamically loading partial views with forms in a div using ajax, all within another form. the master form submits no problem, and one of the partials works but the other doesn't. The ONLY difference between the partial views was at the end of the one that was working was an empty script tag
<script type="text/javascript">
</script>
I removed it and sure enough I got the error. I added an empty script tag to the other partial view and dog gone it, it works! I know it's not the cleanest... but as far as speed and overhead goes...
I know I'm a little late to the party, but I wanted to add another possible solution to this issue. I ran into the same problem on an MVC application I had. The code did not change for the better part of a year and all of the sudden we started receiving these kinds of error messages from the application.
We didn't have multiple instances of the anti-forgery token being applied to the view twice.
We had the machine key set at the global level to Autogenerate because of STIG requirements.
It was exasperating until I got part of the answer here: https://stackoverflow.com/a/2207535/195350:
If your MachineKey is set to AutoGenerate, then your verification
tokens, etc won't survive an application restart - ASP.NET will
generate a new key when it starts up, and then won't be able to
decrypt the tokens correctly.
The issue was that the private memory limit of the application pool was being exceeded. This caused a recycle and, therefore, invalidated the keys for the tokens included in the form. Increasing the private memory limit for the application pool appears to have resolved the issue.
My fix for this was to get the cookie and token values like this:
AntiForgery.GetTokens(null, out var cookieToken, out var formToken);
For those getting this error on Google AppEngine or Google Cloud Run, you'll need to configure your ASP.NET Core website's Data Protection.
The documentation from the Google team is easy to follow and works.
https://cloud.google.com/appengine/docs/flexible/dotnet/application-security#aspnet_core_data_protection_provider
A general overview from the Microsoft docs can be found here:
https://cloud.google.com/appengine/docs/flexible/dotnet/application-security#aspnet_core_data_protection_provider
Note that you may also find you're having to login over and over, and other quirky stuff going on. This is all because Google Cloud doesn't do sticky sessions like Azure does and you're actually hitting different instances with each request.
Other errors logged, include:
Identity.Application was not authenticated. Failure message: Unprotect ticket failed

Access to the path denied, trying to access remote server file

I'm having a hard time trying to access a remote server file from other server with my asp .Net application (C#) hosted in it. The scenario is the following:
I wrote an MVC web application that is hosted on SERVER A; and in some instance, it has to let the user modify a XML configuration file that is located on SERVER B. So, here is the part of the code where I try to read that file from a shared folder on SERVER B:
(C# - Controller)
WindowsImpersonationContext impContext = null;
try
{
impContext = NetworkSecurity.ImpersonateUser(
Settings.Default.ImpersonationDomain,
Settings.Default.ImpersonationUser,
Settings.Default.ImpersonationPass,
LogonType.LOGON32_LOGON_NETWORK,
LogonProvider.LOGON32_PROVIDER_DEFAULT);
}
catch (ApplicationException ex)
{
// write to log file
}
if (null != impContext)
{
try
{
//get the location of the configuration file
string remoteConfigFile = Settings.Default.RemoteDesktopMonitorCnfgFile;
//open the configuration file
XDocument xmlFile = XDocument.Load(#"\\SERVERB\Folder\configurationFile.exe.config");
So, my exception is here, when I try to open that configuration file, I'm getting the exception:
Access to the path '\\SERVERB\Folder\configurationFile.exe.config' is denied
As you could see, I'm impersonating the user before I try to read the file, that impersonation is well done; and I already gave full access to the shared resource to the user that I'm impersonating with. I even tryied joining that user to the Administrators group on both Servers (A and B) and the same exception occurs.
Maybe worth to say that both Servers are on the same Windows Domain, and that I'm using a user account that exists on the domain and that the password is correct.
Any help you could give me would be appreciate.
Thanks in advance.
There is a solution to your problem already on StackOverFlow
Check it out!

Resources