ASP.Net Core Identity login status lost after deploy - asp.net

I am using ASP.Net Core and MS Identity, I try to understand why after each deployment the login users are logged out.
I am running on a IIS 8.5
I have been trying the method in this thread (setting static machine key)
ASP.NET Identity 2 relogin after deploy
by generating static keys at the server level in IIS UI and adding the following to web.config of the website:
<system.web>
<machineKey validationKey="XXX"
decryptionKey="XXX"
validation="SHA1" decryption="AES"/>
</system.web>
However the problem remains:
User logs in
Stop site
Start site
The user needs to log in again
But I also go this:
User logs in
Restart site
The user is still logged in
What can cause the user to be logged off? Any idea on how to avoid that?

(solution split into a separate answer following Chris comment)
I found a solution to keep the login status, it survives website stop/start, and an update of the website source folder:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
// This helps surviving a restart: a same app will find back its keys. Just ensure to create the folder.
.PersistKeysToFileSystem(new DirectoryInfo("\\MyFolder\\keys\\"))
// This helps surviving a site update: each app has its own store, building the site creates a new app
.SetApplicationName("MyWebsite")
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
}
With these additional lines and the machine key set, the login data stays after site stop/start and IIS server restart, and if the site is rebuilt.
More information there: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview
More proposed by justserega:
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced?view=aspnetcore-6.0#data-protection

Authentication use Data Protection Stack. If data protection isn't configured, the keys are held in memory and discarded when the app restarts.
If the key ring is stored in memory when the app restarts:
All cookie-based authentication tokens are invalidated.
Users are required to sign in again on their next request.
Any data protected with the key ring can no longer be decrypted. This may include CSRF tokens and ASP.NET Core MVC tempdata cookies.
You have to configure data protection, more information here https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced?view=aspnetcore-6.0#data-protection

I was able to solve this issue by going into the site's app pool under advanced settings and set "Load User Profile" to true under Process Model.
Then in Startup.cs use:
services.AddSession(options =>
{
//Set whatever options you want here
options.Cookie.IsEssential = true;
options.IdleTimeout = TimeSpan.FromDays(365);
});
...
app.UseSession();
I'm not positive, but services.AddDistributedMemoryCache(); might be needed, helpful or otherwise useful.
I was trying Session State in IIS and machine keys and much, much, more, yet none was needed for just a basic login session persist through a publish or restart.

Related

Automatic Windows Authentication over IIS, ASP.NET 5 and Microsoft SQL Server

There is a lot of relevant Info, I hope I won't forget anything. We want an automatic Windowslogin into an intranet website in our domain.
For testing on a client I'm using a chrome browser that has the website whitelisted.
The Server runs IIS Version 10.0.19041 running a ASP.NET 5 website. It connects to a database MS SQL Server in the same domain.
The Connection String has the parameter Integrated Security=SSPI and works with Apps running locally on clients.
This is what is happening for now: Normally the website would ask for credentials, but since it's in the white list I assume it now uses the Windows credentials, how it's explained here https://stackoverflow.com/a/24875309/15631999
using (SqlConnection Verbindung = new SqlConnection(sqlConnectString))
{
Verbindung.Open();
the "Login failed for user" error is thrown, because it tries to connect with the active webserver user, not with the automatically logged in client. System.Security.Principal.WindowsIdentity.GetCurrent().Name returns the account identity in the AppPool.
What we did: (might not all be relevant ...)
We googled a lot the last couple of days to find out what to do. The general keywords seem to be Windows Authentication, maybe impersonation, NTLM.
So we added the Authentication to the ConfigureServices. Now it looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
services.AddAuthentication(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.Ntlm);
services.AddSession();
services.AddAuthorization(options =>
{
options.AddPolicy("AuthorizationMode", (policy) =>
{
//unauthenticated
policy.RequireAssertion((e) => true);
});
});
//services.AddMvc();
services.AddHttpContextAccessor();
}
We also tried it with IISDefaults.AuthenticationScheme and without Authorization.
Configure Function snippet:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
SessionOptions s = new SessionOptions();
s.IdleTimeout = System.TimeSpan.FromMinutes(30);
s.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
s.Cookie.Name = ".acclaro.de.Session";
s.Cookie.IsEssential = true;
app.UseSession(s);
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
(...)
In IIS/Sites/[Website]/Authentication we disabled Anonymous Authentication and enabled Windows Authentication. This means, I think, I don't have to edit the web.config.
We tried it both with Impersonation enabled and disabled. Basic Authentication seem to always ask for Username and PW. So this is not what we want.
We read that I have to change the order of the Providers. Putting Negotiate under NTLM, or downright deleting it doesn't change anything.
I also read that I have to add SPNs. So I executed setspn -a http/[url] [domain controller computer name] at our domain controller.
I don't know what I'm missing. Maybe I just haven't tried the right combination yet. If anyone can help, that would be appreciated. ( Also I hope impersonating other users won't produce problems, when I write in a local temp folder on the Server. I had some Access denied Exceptions in the past. )
If some relevant info is missing, please tell me.
It's a common misconception that Windows Authentication implies Impersonation. When a user authenticates to a server with Windows Integrated auth, the code on the server doesn't automatically start running as that user.
The normal configuration for an Enterprise web app is for IIS to user Windows Auth to authenticate the users, but to connect to SQL Server using the IIS App Pool Identity.
If you want the users to connect as themselves to SQL Server through the web application, that's called "Impersonation" and you need to enable and configure it. There's a SO question here that shows how to perform impersonation in your middleware. But additional confgiuration, like Kerberos Constrained Delegation configuration may be required.
The downsides of doing this are
It's extra work to configure, and you may need your network admins to help.
If users can connect directly to the database through the app, they can do it through other tools too. So security administration is harder, and riskier.
Users can't reuse connections, so you'll have lots of idle connections.

Sccess some folder on file share based on user authenticated

I have an asp.net web application with forms authentication and users (credentials) are checked against active directory, username is actually samAccountName attribute from AD.
Now I need to enable users to get access to some files which are located on file share, where each user has his own folder.
First proof of concept works like this:
appPool in IIS is configured to run under some domain user, and this user was given R/W access to file share and all user folders
when the user logs into web app only content of the folder on the path "\\myFileServer\username" is visible to him. And same when uploading files they get stored to "\\myFileServer\username".
While this works, doesn't seem to be secure at all. First issue is that user under which application pool runs has access to folders from all users. And even bigger concern is that only username determines to which folder you have access.
So my question is what is the correct/better way to doing this ? I was reading about impersonating the user, but this is not advised anymore if I understood correctly ? And I don't have Windows authentications since the web application must be accessible from internet.
I recommend not running the application under a user account, but creating an application specific account under which it runs with the proper R/W rights, and separate the person who gives these rights from the development team.
Within the application's authentication: after you receive a GET/POST request, you can verify the path to which the current user would read/write data, and cross-reference this with the path the user is authorized to read/write from. If these are incorrect, return a 401 NOT AUTHORIZED response, else, carry on the operation as you do now.
If your endpoints are protected properly, and the application runs under its own account, I don't see any harm in the setup itself. This still however gives the developers a way, through the application, to indirectly access other user's files. Based on how tight these checks must be, you could add additional controls, (like only allowing the application to connect from the production server, and only allowing server transport in a controlled way).
From the Description of your Problem i think Custom HttpHandlers are the right choice for you. You didn't mention what type of files will be present in your Folder , for brevity i will answer by assuming it will be having PDF files.
As you were mentioning that your application will be having different users so for this you need to use .NET built-in authentication manager and role provider. With a simple security framework setup, we'll place a PDF file in the web application, behind a web.config protected folder.then create a custom HTTP handler to restrict access on the static document to only those users who should be allowed to view it.
A sample HTTP Handler:
public class FileProtectionHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
switch (context.Request.HttpMethod)
{
case "GET":
{
// Is the user logged-in?
if (!context.User.Identity.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
return;
}
string requestedFile =
context.Server.MapPath(context.Request.FilePath);
// Verify the user has access to the User role.
if (context.User.IsInRole("User"))
{
SendContentTypeAndFile(context, requestedFile);
}
else
{
// Deny access, redirect to error page or back to login
//page.
context.Response.Redirect("~/User/AccessDenied.aspx");
}
break;
}
}
}
Method SendContentTypeAndFile :
private HttpContext SendContentTypeAndFile(HttpContext context, String strFile)
{
context.Response.ContentType = GetContentType(strFile);
context.Response.TransmitFile(strFile);
context.Response.End();
return context;
}
private string GetContentType(string filename)
{
// used to set the encoding for the reponse stream
string res = null;
FileInfo fileinfo = new FileInfo(filename);
if (fileinfo.Exists)
{
switch (fileinfo.Extension.Remove(0, 1).ToLower())
{
case "pdf":
{
res = "application/pdf";
break;
}
}
return res;
}
return null;
}
Last step is that you need to configure this HTTP Handler in the webconfig ,
and You can see the more info here
Here is the complete Source Code
You're architecture (and assumptions) seem good for a low/mid security level, but if the nature of your data is very sensitive (medical, etc) my biggest concern about security would be controlling the user sessions.
If you're using forms authentication then you're storing the authenticated identity in a cookie or in a token (or if you're using sticky sessions then you're sending the session Id, but for the case it's the same). The problem arises if user B has phisical access to the machine where user A works. If user A leaves it's workplace (for a while or forever) and he doesn't explicitly close it's session in your web app, then his identity has been left around, at least until his cookie/token expires, and user B can use it since the identity system of ASP.NET hasn't performed a SignOut. The problem is even worse if you use tokens for authorization, because in all the infamous Microsoft implementations of the Identity System you're responsible of providing a way to invalidate such tokens (and make them dissapear from the client machine) when the user signs out, since they would stay valid until it's expiration. This can be addressed (but no completely thus not very satisfactorily for high security requirements) issuing short living refresh tokens, but that's another story, and I don't know if it's your case. If you're going with cookies then when user A signs out it's cookie is invalidated and removed from the request/response cicle, so this problem is mitigated. Anyway you should ensure that your users close their sessions in your web app or/and configure the cookies with short lives or short sliding expirations.
Other security concerns may be related with CSRF, wich you can prevent using the Antiforgery Token infrastructure of ASP.NET, but these kind of attacks are methods that are very far away from the tipical user (I don't know anything about the nature of your user and if your app is exposed to public on internet or it's only accesible on an intranet), but If you worry for such specialised attacks and have so sensitive data, maybe you should go with something more complex than forms authentication (two factor, biometrical, etc)

ASP.NET MVC 4 single use impersonation

I am developing an ASP.NET MVC 4 application that utilizes impersonation (running as domain user), set in web.config and setup app pool to run as same user. Within the application we use a mix of windows authentication and a custom role provider to restrict access.
A new requirement has been brought about that requires querying data from a table locked down by AD groups. For security reasons, we cannot at our generic user to this table, or control it via custom roles. The only way our DBA will allow this data to leave it's respective home is when it is to one of the users within the AD groups assigned. So, I need to be able to make a call to an EF stored proc or Repo method as the current request's user.
I have tried something like the following (I hope this might clear up what I am attempting to do):
[HttpGet]
public ViewResult TestCall()
{
var lst = new List<Staged>();
//Get current request user
using (var person = ((WindowsIdentity) HttpContext.User.Identity))
{
//Impersonate said user
person.Impersonate();
//Make magics happen
using (var db = new TestEntities())
{
lst.AddRange(db.up_testseccall());
}
}
//Return magics, hopefully.
return View(lst.ToArray());
}
This code works fine locally, but fails miserably when tested remotely with a test account of mine. Any ideas or pointers would be greatly appreciated.
EDIT:
The issue I'm having now is that the DB is recieving the login as 'NT Authority\Anonymous Login'
EDIT 2:
Managed to receive a 'Safe handle has been closed' exception...so that's something new.
EDIT 3:
Current connection string
EDIT 4:
I gave up on this approach after a week of trying to get kerberos double-hop setup. I opted for a special domain service account for handling the required transactions.
<add name="TestEntities" connectionString="metadata=res://*/Models.TestModel.csdl|res://*/Models.TestModel.ssdl|res://*/Models.TestModel.msl;provider=System.Data.SqlClient;provider connection string="data source=DATABASENAME;initial catalog=CATALOG;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />

WIF cross-domain on one IIS site, dynamically setting of realm

We have a lot of domains running on one IIS WebSite/AppPool.
Right now we are in the process of implementing SSO with Windows Identity Foundation.
in web.config the realm has to be set with
<wsFederation passiveRedirectEnabled="true" issuer="http://issuer.com" realm="http://realm.com" requireHttps="false" />
My problem is that the realm is dependent on which domain the user accessed the website on
so what I did is that I set it in an global action filter like this
var module = context.HttpContext.ApplicationInstance.Modules["WSFederationAuthenticationModule"] as WSFederationAuthenticationModule;
module.Realm = "http://" + siteInfo.DomainName;
My question is. When I set the realm like this, is it set per user instance
or application instance.
Scenario.
User A loads the page and the realm get set to domain.a.com.
User B is already logged in on domain.b.com and presses login.
Since user A loaded the page before User B pressed login, user A will hit the STS
with the wrong realm set.
What will happen here?
If this is not the way to set the realm per user instance, is there another way to do it?
I have already solved the problem.
I set PassiveRedirectEnabled to false in web.config
I set up the mvc project to use forms authentication, eventhough I dont.
I do that so that I will get redirected to my login controller with a return url everytime a controller with [Authorize] is run.
In my login controller I do
var module = HttpContext.ApplicationInstance.Modules["WSFederationAuthenticationModule"] as WSFederationAuthenticationModule;
module.PassiveRedirectEnabled = true;
SignInRequestMessage mess = module.CreateSignInRequest("passive", returnUrl, false);
mess.Realm = "http://" + Request.Url.Host.ToLower();
HttpContext.Response.Redirect(mess.WriteQueryString());
This is definitely not really how it should be, for me it feels like Windows Identity Foundation is lagging behind, both in documentation and microsoft technology wise, no examples for MVC.
For other MVC people i recommend them to not use the fedutil wizard, and instead write the code and configuration themself

File permissions with FileSystemObject - CScript.exe says one thing, Classic ASP says another

I have a classic ASP page - written in JScript - that's using Scripting.FileSystemObject to save files to a network share - and it's not working. ("Permission denied")
The ASP page is running under IIS using Windows authentication, with impersonation enabled.
If I run the following block of code locally via CScript.exe:
var objNet = new ActiveXObject("WScript.Network");
WScript.Echo(objNet.ComputerName);
WScript.Echo(objNet.UserName);
WScript.Echo(objNet.UserDomain);
var fso = new ActiveXObject("Scripting.FileSystemObject");
var path = "\\\\myserver\\my_share\\some_path";
if (fso.FolderExists(path)) {
WScript.Echo("Yes");
} else {
WScript.Echo("No");
}
I get the (expected) output:
MY_COMPUTER
dylan.beattie
MYDOMAIN
Yes
If I run the same code as part of a .ASP page, substituting Response.Write for WScript.Echo I get this output:
MY_COMPUTER
dylan.beattie
MYDOMAIN
No
Now - my understanding is that the WScript.Network object will retrieve the current security credentials of the thread that's actually running the code. If this is correct - then why is the same user, on the same domain, getting different results from CScript.exe vs ASP? If my ASP code is running as dylan.beattie, then why can't I see the network share? And if it's not running as dylan.beattie, why does WScript.Network think it is?
Your problem is clear. In the current implementation you have only impersonation of users and no delegation. I don't want to repeat information already written by Stephen Martin. I only want to add at least three solutions. The classical way of delegation which Stephen Martin suggests is only one way. You can read some more ways here: http://msdn.microsoft.com/en-us/library/ff647404.aspx#paght000023_delegation. I see three practical ways of you solving your problem:
Convert the impersonation token of the user to a token with delegation level of impersonation or to a new primary token. You can do this with respect of DuplicateToken or DuplicateTokenEx.
Use S4U2Self (see http://msdn.microsoft.com/en-us/magazine/cc188757.aspx and http://msdn.microsoft.com/en-us/library/ms998355.aspx) to receive a new token from the old one with respect of one simple .NET statement WindowsIdentity wi = new WindowsIdentity(identity);
You can access another server with respect of one fixed account. It can be a computer account on an account of the application pool of the IIS. It can be another fixed defined account which one will only use for access to the file system.
It is important to know which version of Windows Server you have on the server where IIS is running and which Domain Function Level you have in Active Directory for your Domain (you see this in "Active Directory Domain and Trusts" tool if you select your domain and choose "Raise Domain Functional Level"). It is also interesting to know under which account the application pool of the IIS runs.
The first and the third way will always work. The third way can be bad for your environment and for the current permission in the file system. The second one is very elegant. It allows control of which servers (file server) are accessed from IIS. This way has some restrictions and it needs some work to be done in Active Directory.
Because you use classic ASP, a small scriptable software component must be created to support your implementation.
Which way do you prefer?
UPDATED based on the question from comment: Because you use classic ASP you can not use a Win32 API directly, but you can write a small COM component in VB6 or in .NET which use APIs which you need. As an example you can use code from http://support.microsoft.com/kb/248187/en. But you should do some other things inside. So I explain now which Win32 API can help you to do everything what you need with tokens and impersonation.
First of all a small explanation about impersonation. Everything works very easy. There are always one primary token under which the process runs. To any thread another token (thread token) can be assigned. To do this one needs to have a token of a user hUserToken and call API ImpersonateLoggedOnUser(hUserToken);.
To go back to the original process token (for the current thread only) you can call RevertToSelf() function. The token of user will be received and already impersonated for you by IIS, because you so configured your Web Site. To go back to the original process token you should implement calling of the function RevertToSelf() in your custom COM component. Probably, if you need to do nothing more in the ASP page, it will be enough, but I recommend you be more careful and save current users token in a variable before operation with files. Then you make all operations with file system and at the end reassign users token back to the current thread. You can assign an impersonation token to a thread with respect of SetThreadToken(NULL,hUserToken);. To give (save) current thread token (user token in your case) you can use OpenThreadToken API. It must work.
UPDATED 2: Probably the usage of RevertToSelf() function at the end of one ASP page would be already OK for you. The corresponding C# code can be so:
Create a new Project in C# of the type "Class Library" with the name LoginAdmin. Paste the following code inside
using System;
using System.Runtime.InteropServices;
namespace LoginAdmin {
[InterfaceTypeAttribute (ComInterfaceType.InterfaceIsDual)]
public interface IUserImpersonate {
[DispId(1)]
bool RevertToSelf ();
}
internal static class NativeMethods {
[DllImport ("advapi32.dll", SetLastError = true)]
internal static extern bool RevertToSelf ();
}
[ClassInterface (ClassInterfaceType.AutoDual)]
public class UserImpersonate : IUserImpersonate {
public UserImpersonate () { }
public bool RevertToSelf () {
return NativeMethods.RevertToSelf();
}
}
}
Check in project properties in "Build" part "Register for COM interop". In "Signing" part of the project check Sign the assembly and in "Choose a strong name key file" choose <New...>, then type any filename and password (or check off "protect my key..."). At the end you should modify a line from AssemblyInfo.cs in Properties part of the project:
[assembly: ComVisible (true)]
After compiling this project you get two files, LoginAdmin.dll and LoginAdmin.tlb. The DLL is already registered on the current computer. To register if on the other computer use RegAsm.exe.
To test this COM DLL on a ASP page you can do following
<%# Language="javascript" %>
<html><body>
<% var objNet = Server.CreateObject("WScript.Network");
Response.Write("Current user: ");Response.Write(objNet.UserName);Response.Write("<br/>");
Response.Write("Current user's domain: ");Response.Write(objNet.UserDomain);Response.Write("<br/>");
var objLoginAdmin = Server.CreateObject("LoginAdmin.UserImpersonate");
var isOK = objLoginAdmin.RevertToSelf();
if (isOK)
Response.Write("RevertToSelf return true<br/>");
else
Response.Write("RevertToSelf return false<br/>");
Response.Write("One more time after RevertToSelf()<br/>");
Response.Write("Current user: ");Response.Write(objNet.UserName);Response.Write("<br/>");
Response.Write("Current user's domain: ");Response.Write(objNet.UserDomain);Response.Write("<br/>");
var fso = Server.CreateObject("Scripting.FileSystemObject");
var path = "\\\\mk01\\C\\Oleg";
if (fso.FolderExists(path)) {
Response.Write("Yes");
} else {
Response.Write("No");
}%>
</body></html>
If the account used to run the IIS application pool has access to the corresponding network share, the output will be look like following
Current user: Oleg
Current user's domain: WORKGROUP
RevertToSelf return true
One more time after RevertToSelf()
Current user: DefaultAppPool
Current user's domain: WORKGROUP
Yes
Under impersonation you can only access securable resources on the local computer you cannot access anything over the network.
On Windows when you are running as an impersonated user you are running under what is called a Network token. This token has the user's credentials for local computer access but has no credentials for remote access. So when you access the network share you are actually accessing it as the Anonymous user.
When you are running a process on your desktop (like CScript.exe) then you are running under an Interactive User token. This token has full credentials for both local and remote access, so you are able to access the network share.
In order to access remote resources while impersonating a Windows user you must use Delegation rather then Impersonation. This will involve some changes to your Active directory to allow delegation for the computer and/or the users in your domain. This can be a security risk so it should be reviewed carefully.

Resources