.NET Core Web Application - app_offline equivalent / Safely stop web application for maintenance - apache

If hosting a web application with IIS I know there's an app_offline.htm file that can be used, but I'm hosting a .NET core web app in a Linux environment with apache. Anyone know what the safest approach is to taking an app offline in this situation so that I can make changes to my app without breaking anything?
EDIT: The intent is to keep the website online, but prevent login or any interactions within a web application until maintenance tasks are complete and app restarted.

After a little research, I made something that should serve the purposes of an 'app_offline' by using an ActionFilter.
Using dependency injection, this filter takes a boolean value set in my appsettings.json and if set to true it's read into the action filter and redirects users to a 'down for maintenance' page. It is my understanding that using IOptionsSnapshot instead of IOptions will get the value if changed during runtime. This is also nice when you have a public facing website but only want the backend application to prevent any actions until changes have been made and the app restarted. Below is an example of my ActionFilter. Only thing I don't like is that this filter has to be set on pretty much every controller in the app. I'd prefer coming up with something that could be checked sooner in the pipeline, so if anyone has a better approach I'd love to see it.
private readonly ServiceSettings _settings;
public AppStateAttribute(IOptionsSnapshot<ServiceSettings> options)
{
_settings = options.Value;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if(_settings.AppOffline)
{
var redirectOnFail = "/maintenance";
context.HttpContext.Response.Redirect(redirectOnFail, true);
}
base.OnActionExecuting(context);
}

Related

Random ASP.NET Core Identity Logouts in production

I have an ASP.NET Core MVC web application that uses Identity to handle user account Authentication and Authorization. When running the app on my local IIS express everything was working properly. After deploying the app to a shared web server I started to notice that the logged-in user accounts would get logged out at seemingly random intervals. Through experimentation, I was able to determine that the log-outs were occurring whether the account was active or idle. They were occuring at no recuring time interval and completely unrelated to any expiry time that I set on my cookies. The logouts were occuring on every view in the web app so I couldn't pin the issue to any particular controller. Also I use the same Database for the published and the local testing version of the app and therefore the same user accounts. I anyone has an idea where to start looking for a solution it would be greatly appreciated.
I posted this question because there is a great answer that 90% solves the issue Here however of the multiple forums that I have been scouring over the last few days there are none with an accepted answer. I am posting this answer to address this. The underlying cause of the issue is that IIS application pool is being reset or recyling and on a shared host with multiple applications using it this can be happening fairly frequently. As is suggested in the above link Data Protection has to be used to persist the keys if IIS application pool recycles. Here is the code offered in the original answer.
services.AddDataProtection()
.PersistKeysToFileSystem(new System.IO.DirectoryInfo("SOME WHERE IN STORAGE"))
//.ProtectKeysWithCertificate(new X509Certificate2());
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
This code is to be added in ConfigureServices in Startup.cs
As my application is being hosted on a shared server using .PersistKeysToFileSystem was not an option so instead I persisted the keys using DbContext like this:
services.AddDataProtection().PersistKeysToDbContext<MyKeysContext>()
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
Based on This article here I build MyKeysContext as follows.
// Add a DbContext to store your Database Keys
services.AddDbContext<MyKeysContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("MyKeysConnection")));
In ConfigureServices in Startup.cs and then created a class called MyKeysContext as follows:
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;
namespace WebApp1
{
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; }
}
}
I created the database on my Host this will probably be different so I have omitted this step. then I applied the migrations to the database like this.
Add-Migration AddDataProtectionKeys -Context MyKeysContext
Update-Database -Context MyKeysContext

Suspected bug in Microsoft Identity Platform with ASP.NET Core 3.1 Razor Pages

I am developing an application to be hosted in the Azure App Services environment which consists of a front-end Web App, a back-end Web API and a SQL Database (using Azure SQL). The front-end Web App is a Razor Pages app. We are trying to use the Microsoft Identity Platform (via Microsoft.Identity.Web and Microsoft.Identity.Web.UI libraries) to acquire an access token for the API when needed.
It works perfectly well the first time, but once a token has been acquired and cached - if the application is restarted it fails with this error:
IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
No account or login hint was passed to the AcquireTokenSilent call.
Startup configuration is (I've tried various variants of this):
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.HandleSameSiteCookieCompatibility();
});
services.AddOptions();
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["Api:Scopes"] })
.AddInMemoryTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages().AddRazorRuntimeCompilation().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddMvc();
//Other stuff...
}
I have tried for many days trying to find either a resolution workaround for this. I can catch the
error, but there is no action we can take programmatically that seems to clear the problem (the ITokenAcquisition interface does not offer the option to force an interactive login).
I have found that it is ONLY a problem in a Razor Pages application - a controller-based MVC Web App with almost identical startup code does not exhibit the problem.
I have also found that, by creating a controller-based test MVC Web App and configuring it with the same client id, tenant id etc. as the app we're having problems with, then starting it up (within the Visual Studio development environment) as soon as the main app gets the problem, I can clear the error condition reliably every time. However this is obviously not a viable long-term solution.
I have searched for this problem on every major technical forum and seen a number of similar sorts of issues raised, but none provides a solution to this precise problem.
To replicate:
Create an ASP.NET Core 3.1 Web API.
Create an ASP.NET Core 3.1 Razor Pages Web App that calls the API.
Register both with Azure Active Directory and configure the App to request a token to access the API (as per various MS documents).
Run - if everything is set up correctly the login screen will appear and all will work correctly.
Stop the Web App, wait a couple of minutes and re-start. The error above will now appear.
I have raised a Microsoft support request for it - has anybody else come across this and found a solution for it?
I have finally got to the bottom of this, largely thanks to this: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/216#issuecomment-560150172
To summarise - for anyone else having this issue:
On the first invocation of the web app you are not signed in, and so get redirected to the Microsoft Identity Platform login, which logs you in and issues an access token.
The access token is stored in the In-Memory token cache through the callback.
All then works as expected because the token is in the cache.
When you stop, and then re-start the web app within a reasonably short time, it uses the authentication cookies to pick up the still-current login, and so it does not access the Identity Platform and you do NOT get an access token.
When you ask for a token the cache is empty - so it throws the MsalUiRequiredException.
What isn't really made clear in any of the documentation is that this is supposed to happen - and that exception is picked up by the "AuthorizeForScopes" attribute but only if you allow the exception to fall all the way through and don't try to handle it.
The other issue is that in a Razor Pages app the normal AuthorizeForScopes attribute has to go above the model class definition for every page - and if you miss one it may trigger the above problem.
The solution proposed by "jasonshave" in the linked article solves that problem by replacing the attribute with a filter - so it will apply to all pages.
Maybe I'm a bit old-school, but the idea of using an unhandled exception as part of a planned program control flow doesn't sit right with me - at the very least it should be made clear that that's the intention. Anyway - problem now solved.

How do I get a connection string in a .net core standard class library from the configuration file in a .net core 2.0 web app?

I have .net core standard class library which is essentially a DAL with several class methods that return collections and objects from a database. The connection string is in the appsettings.json file of the ASP.net 2 core web app. I also want to access this class library from a console app project where the configuration file with the connection string will be present in that console app project.
This was simple in .net prior to .net core. The DAL class library would just access the web.config from a web project and an app.config from a console application as it the library is referenced in both the web app and console apps. But it doesn't seem like this is at all possible.
I'm looking for the simple solution in .net core to get a connection string from web app or console app as the case may be.
Where you're probably going wrong is that you want to access configuration from your class library, but then you want to leak details specifically about the caller (That it will have a web.config).
But what if you decide in your Web Application you want to use Azure Key Vault or another secrets mechanism? Does your class library need to then change it's entire implementation to use Key Vault? And then does that mean your console application also has no option but to use Key Vault too?
So the solution is to use dependency inversion. Put simply, let's say I have code like the following :
interface IMyRepositoryConfiguration
{
string ConnectionString {get;}
}
class MyRepositoryConfiguration : IMyRepositoryConfiguration
{
public string ConnectionString {get;set;}
}
class MyRepository
{
private readonly IMyRepositoryConfiguration _myRepositoryConfiguration;
public MyRepository(IMyRepositoryConfiguration myRepositoryConfiguration)
{
_myRepositoryConfiguration = myRepositoryConfiguration;
}
}
Now in my startup.cs I can do something like :
services.AddSingleton<IMyRepositoryConfiguration>(new MyRepositoryConfiguration {//Set connection string from app settings etc});
And now my class library doesn't need to know exactly how those configuration strings are stored or how they are fetched. Just that if I request an instance of IMyRepositoryConfiguration, that it will have the value in there.
Alternatively of course, you can use the Options class too, but personally I prefer POCOs. More info here : https://dotnetcoretutorials.com/2016/12/26/custom-configuration-sections-asp-net-core/
It is very much possible to access "connection strings" or other configuration data easily in .Net core without much additional effort.
Just that the configuration system has evolved (into something much better) & we have to make allowances for this as well (& follow recommended practices).
In your case as you are accessing the connection string value in a standard library (intended to be reused), you should not make assumptions as how the configuration values will be "fed" to your class. What this means is you should not write code to read a connection string directly from a config file - instead rely on the dependency injection mechanism to provide you with the required configuration - regardless of how it has been made available to your app.
One way to do this is to "require" an IConfiguration object to be injected into your class constructor & then use the GetValue method to retrieve the value for the appropriate key, like so:
public class IndexModel : PageModel
{
public IndexModel(IConfiguration config)
{
_config = config;
}
public int NumberConfig { get; private set; }
public void OnGet()
{
NumberConfig = _config.GetValue<int>("NumberKey", 99);
}
}
In .net core, before the app is configured and started, a "host" is configured and launched. The host is responsible for app startup and lifetime management. Both the app and the host are configured using various "configuration providers". Host configuration key-value pairs become part of the app's global configuration.
Configuration sources are read in the order that their configuration providers are specified at startup.
.Net core supports various "providers". Read this article for complete information on this topic.

Sitecore with LDAP - authenticate programmatically

I am creating an app inside Sitecore and I only want it available to the users via a direct URL. I want the authentication to occur against LDAP. I tried going directly to the app and let it redirect to the auto login page and redirect me to the app but it didn't do it. Instead it took me to the Sitecore login page.
I'm wondering if it is possible for me to write some code to auto authenticate a LDAP user and redirect to the app page. I want the user to never see the login page or Sitecore desktop or any of the Sitecore screens other than that one app.
Thanks
We accomplished something similar by combining the AD module with some custom code in the Global.asax. Below are a few lines that might be helpful. You'll likely need a bunch of logic to check if the user is already logged in, and whether they are accessing a path you want to auto-login for.
NOTE: Make sure windows authentication is enabled in IIS.
protected void Session_Start(object sender, EventArgs e){
// The user from Windows Authentication in IIS
var user = Context.Request.ServerVariables["LOGON_USER"];
//Log the user in
bool success = Sitecore.Security.Authentication.AuthenticationManager.Provider.Login(user, false);
}
You'll note that the sample I provided goes directly to the provider. You can also call Login at the AuthenticationManager class, and this will also do some other work with cache. In my case, I was trying to bypass that.
UPDATE (2017-06-29):
In newer versions of Sitecore it is not recommended to make changes to the Global.asax. Unfortunately, there is no equivalent pipeline in Sitecore. You can attempt to use httpRequestBegin (where the UserResolver processor is) or httpRequestProcessed, but these will fire on every single request.
One alternative (credit to #Mark Cassidy on SlackChat) is to use the Initialize pipeline and in that processor register to the session start event.
It is possible yes, a quick Google search turned up these:
http://www.nehemiahj.com/2013/01/how-to-enable-single-sign-on-in-sitecore.html
based on the AD Module from Sitecore
http://sdn.sitecore.net/SDN5/Products/AD/AD11/Documentation.aspx
That should give you a good place to start from.

WCF Client Configuration in a Sharepoint Webpart

I have a Sharepoint 2010 webpart that calls a WCF service.
I've created a service proxy and manually coded the endpoint, see below.
In a conventional WCF client I'd use the config files for the configuration and use transforms when I was buiding for deployment to different environments.
How would I achieve the same through a Sharepoint webpart? I want to put the configuration somewhere that it can be changed for different build configurations.
ie. For a local deployment during testing, then a test server, production. We're trying to automate this as much as possible.
Thanks,
Tim
UPDATE:
I'm aware that you need to put config data in the web.config file in sharepoint. I'm looking for a way to put these config settings into source control and have them automatically populate / deploy for different builds and environments.
namespace CombinedPortal.WcfClient {
public class FrameworkServiceProxy : IFrameworkService
{
private IFrameworkService _proxy;
public FrameworkServiceProxy()
{
var endpoint = new EndpointAddress("http://server:1234/FrameworkService.svc");
var binding = new WSHttpBinding(SecurityMode.None);
_proxy = new ChannelFactory<IFrameworkService>(binding, endpoint).CreateChannel();
}
public Framework GetCurrentFramework(double uniqueLearnerNumber)
{
var fw = _proxy.GetCurrentFramework(uniqueLearnerNumber);
return fw;
}
} }
Your code is C# code which executes on the server.
When then user presses a button on a web part there is a POST back to the Sharepoint web server, where the C# code executes.
It is therefore the web.config of your SharePoint site which is used.