IIS Express - temporarily impersonate the authenticated user - asp.net

According to this document:
http://msdn.microsoft.com/en-us/library/ff647405.aspx
I've set the following in web.config
<authentication mode="Windows" />
<identity impersonate="false" />
And have also set this in the applicationhost.config for IIS Express 7.5
<anonymousAuthentication enabled="false" userName="" />
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>
But System.Threading.Thread.CurrentPrincipal.Identity still always equals the Windows identity of the authenticated user, i.e. not the account that IISExpress.exe is running under (my dev account).
To be clear, I'm logged-in as Account A and IIS Express runs as Account A, but I call my web service using Account B (setting the Credentials on HttpWebRequest) but the server-side code runs as Account B, i.e. the thread has this id and I can access network resources.
I'd like execution to occur as Account A (and on the prod server, as a service account) and only impersonate when I want it to.
Am I doing something wrong or is this area not perfectly implemented in IISX?
Thanks
Luke
Update 1
So, I thought I figured-out what was going on; see my answer below. The problem is that it seems to be working in reverse!
string n1 = System.Security.Principal.WindowsIdentity.GetCurrent().Name; // Runtime account.
string n2 = System.Threading.Thread.CurrentPrincipal.Identity.Name; // Calling account.
var winId = (System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity;
System.Security.Principal.WindowsImpersonationContext ctx = null;
try
{
bool b = System.IO.File.Exists(#"d:\p\p.txt"); // true (!)
using (ctx = winId.Impersonate())
{
// Now impersonating. Access (local) resources using the identity of the authenticated user.
n1 = System.Security.Principal.WindowsIdentity.GetCurrent().Name; // Calling account.
n2 = System.Threading.Thread.CurrentPrincipal.Identity.Name; // Calling account.
b = System.IO.File.Exists(#"d:\p\p.txt"); // false (!)
}
...
The folder d:\p is set to only allow the calling account access, which is fine when tested in DOS but from my web service, it has access and I expect this is because the thread has the caller's security context, BEFORE I've begun impersonating!
Weirder still, when I do impersonate, I suddenly lose access to it!
I'm going to create a test project on a proper IIS 7.5 server and see if this is a bug in IIS Express.
Update 2
The problem with the Exists test has been half-solved. I removed rights to the folder but the file itself still had some rights, and the way .NET accesses files without traversing the folder means it could still access it.
Now I get
b == false // as expected.
...
b == false // unexpected, after impersonation I should be able to see this file.
I'd expect impersonation to give me access, it doesn't.
Update 3
I've given up. Impersonation doesn't work and I can only assume its a network policy or some undiscoverable hidden setting.

Got it. Sort of.
string n1 = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
string n2 = System.Threading.Thread.CurrentPrincipal.Identity.Name;
n1 = the process identity
n2 = the caller's identity
The thread's security context has the caller's identity, which I didn't expect. I thought the thread would have the context of the process flowed to it, but this is clearly not how it works.
I now have an interesting situation that when I call .Impersonate on the callers WindowsIdentity, I still can't access a local file permissioned for the calling account, but I'll work that out and update my answer.
SEE UPDATE IN QUESTION

Related

When I am deploying by ASP.NET MVC application on IIS I am specifying my credentials in Identity as a setting in DefaultAppPool

Also if i don't give my credentials it gives some configuration file error.
I want AD authentication to take place. But it always picks my username when logged from different systems. How do I resolve it?
Is there any way I can make a user group and assign that to the Identity in DefaultAppPool or do I need to change the code for authentication as it is only taking the user from the Identity mentioned in DefaultAppPool?
The code that I am using to validate the user who logs in is as follows:
string groupName = ConfigurationManager.AppSettings["groupName"].ToString();
string domainName = ConfigurationManager.AppSettings["domain"].ToString();
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domainName);
UserPrincipal user = UserPrincipal.FindByIdentity(ctx,IdentityType.SamAccountName,System.Security.Principal.WindowsIdentity.GetCurrent().Name);
if (user != null)
{ //code as required }
code in web.config file:
<appSettings>
<add key="domain" value="EYDEV" />
<add key="groupName" value="Domain Users" />
</appSettings>
WindowsIdentity.GetCurrent() gets the credentials that your application is running under, not the current user.
You need to first enable Windows Authentication by adding this to your system.web section of your web.config:
<authentication mode="Windows" />
See Microsoft's documentation for more details (like making sure you have the right IIS features installed): https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/security/authenticating-users-with-windows-authentication-cs
Once that is setup, you can use User.Identity.Name in a Controller to get the username of the current user.

RedisSessionStateProvider Elasticache deadlock

we want to share ASP.NET Session state between our apps and services. We chose Elasticache/redis to achieve this. It was going well but we've run into a deadlock scenario.
Here's the deadlock sequence:
user navigates to page served by App 1
App 1 uses RedisSessionStateProvider, successfully fetches the Session in a few milliseconds
App 1 makes an HttpWebRequest to App 2, with the ASP.NET_SessionId cookie attached
App 2 also uses RedisSessionStateProvider, which attempts to fetch the Session from the same redis instance and times-out after ~ 2 minutes
Presumably App 1's RedisSessionStateProvider is holding a (write?) lock on the cache item containing the Session. As you can tell from my parlance, I'm no redis guru...
AFAICT Elasticache gives you no visibility onto situations like this, just performance-y graphs. And RedisSessionStateProvider is closed-source so I can't poke around there.
I also tried to get RedisSessionStateProvider to log (via the loggingClassName parameter) but nothing gets written by either App 1 or App 2 (my Log() method is called though).
To prove that it is the RedisSessionStateProvider deadlocking (rather than our own code deadlocking) I switched App 1 back to using InProc sessions and everything runs fine.
Does anyone have any suggestions? BTW our Session data is to all intents and purposes immutable, so there really is no need for it to be locked.
Many thanks,
Pete
EDIT: the sessionState config as requested. Note that the large operationTimeoutInMilliseconds value is so that we don't get exceptions whilst debugging the app. This will be changed to ~ 5000 in production.
<sessionState mode="Custom" customProvider="RedisSessionProvider">
<providers>
<add name="RedisSessionProvider"
type="Microsoft.Web.Redis.RedisSessionStateProvider"
host = "ec2-184-73-3-249.compute-1.amazonaws.com"
port = "6379"
ssl = "false"
throwOnError = "true"
retryTimeoutInMilliseconds = "2000"
applicationName = "PE"
connectionTimeoutInMilliseconds = "2000"
operationTimeoutInMilliseconds = "1800000"
</providers>
</sessionState>
This is not answer but it is not fitting in comment section.
At start of page asp.net page execution life cycle it calls GetItemExclusive which fetches session from store (in this case redis) and puts a lock on that session so other parallel request cannot modify session while this request is working. This lock has time out that is equivalent of request timeout that you can set using web.config like below.
<configuration>
<system.web>
<httpRuntime executionTimeout="10"/>
</system.web>
</configuration>
Now, page executes and depending on weather anything was modified or not modified in session it calls SetAndReleaseItemExclusive or ReleaseItemExclusive which releases the lock. If this request fails for some reason than it will retry depending on retryTimeoutInMilliseconds value. If retryTimeoutInMilliseconds is very less or same as operationTimeoutInMilliseconds then it might not retry at all. If SetAndReleaseItemExclusive or ReleaseItemExclusive is not completed successfully then basically your session will be locked for complete time of “executionTimeout” that you have set above which is in seconds. All other request will be blocked and won’t be able to access the session while it is locked. Lock will be released automatically when it reaches expiry.
Use web.config properties loggingClassName and loggingMethodName for configuring logging. You can find more details in web.config comments when you upgrade to above package. You can basically provide a public, static method which returns a TextWriter. Session state provider and StackExchange.Redis.StrongName both will use this TextWriter object to log details.
This will help us get more details about problem. Beware that enabling logging will reduce the performance.
Example of using logging:
namespace SSPWebAppLatest3
{
public static class Logger
{
public static TextWriter GetLogger()
{
return File.CreateText("C:\\Logger.txt");
}
}
}
Web.config:
<add name="MySessionStateStore" type="Microsoft.Web.Redis.RedisSessionStateProvider" host="127.0.0.1" accessKey="" ssl="false"
loggingClassName="Logger, SSPWebAppLatest3, Version=1.0.0.0, Culture=neutral ……."
loggingMethodName="GetLogger"/>
Please send me a reproducible test app with which I debug this further. You can also do the same as session state and output cache provider code is open source now. (https://github.com/Azure/aspnet-redis-providers)

How to set machineKey on Azure Website

I'm running an Azure Website. Whenever I deploy, everyone gets logged out because the machineKey changes.
I specified the machineKey in the web.config but this didn't solve the issue. I believe this is because Azure automatically overwrites the machineKey [1].
I've found a couple of similar questions here but the answers link to dead links.
So, what's the solution? Surely there's a way to keep users logged in regardless of deployments on Azure.
Try to reset the machine-key configuration section upon Application_Start:
protected void Application_Start()
{
// ...
var mksType = typeof(MachineKeySection);
var mksSection = ConfigurationManager.GetSection("system.web/machineKey") as MachineKeySection;
var resetMethod = mksType.GetMethod("Reset", BindingFlags.NonPublic | BindingFlags.Instance);
var newConfig = new MachineKeySection();
newConfig.ApplicationName = mksSection.ApplicationName;
newConfig.CompatibilityMode = mksSection.CompatibilityMode;
newConfig.DataProtectorType = mksSection.DataProtectorType;
newConfig.Validation = mksSection.Validation;
newConfig.ValidationKey = ConfigurationManager.AppSettings["MK_ValidationKey"];
newConfig.DecryptionKey = ConfigurationManager.AppSettings["MK_DecryptionKey"];
newConfig.Decryption = ConfigurationManager.AppSettings["MK_Decryption"]; // default: AES
newConfig.ValidationAlgorithm = ConfigurationManager.AppSettings["MK_ValidationAlgorithm"]; // default: SHA1
resetMethod.Invoke(mksSection, new object[] { newConfig });
}
The above assumes you set the appropriate values in the <appSettings> section:
<appSettings>
<add key="MK_ValidationKey" value="...08EB13BEC0E42B3F0F06B2C319B..." />
<add key="MK_DecryptionKey" value="...BB72FCE34A7B913DFC414E86BB5..." />
<add key="MK_Decryption" value="AES" />
<add key="MK_ValidationAlgorithm" value="SHA1" />
</appSettings>
But you can load your actual values from any configuration source you like.
If Azure is rewriting your machineKey, you can't do much about it, as it is part of their infrastructure. However, there are other methods.
Override FormsAuthentication
This should not be difficult as you can easily look up for source code of FormsAuthentication and create your own logic and replace MachineKey with your own key stored in web.config or in your database.
Custom Authentication Filter
The simplest way would be to create a filter and check, verify, encrypt decrypt cookies in your filter. You need to do this on OnAuthorization method and create new instance of IPrincipal and set IsAuthenticated to true if descryption was successful.
OAuth
Enable OAuth and create OAuthProvider. However you will need to host OAuthProvider on server that is in your control as that will need machineKey working.
Enable Third Party OAuth, if you enable OAuth with Google, Facebook etc, it will be easy as user will be redirected to OAuth provider and they will continue to login automatically and a new session will be established.
I had the same issue and in my case I was using the webdeploy to Azure wizard in VS13. I thought I was going crazy as I would set the machinekey in the web.config and then it would be changed on the deployed web.config to autogenerate. It is something in the webdeploy script/settings. My solution was to open the live azure site from within VS13 using the Server Explorer and then editing the web.config and saving changes. This preserved my settings with my supplied keys and all works fine.

Forms Authentication SSO - Why does User.Identity.IsAuthenticated == false

I'm trying to set up SSO on two separate IIS web sites (with a common domain) using Forms Authentication. I'm using the common approach of setting the auth cookie to the common domain and using matching machine key to enable decryption.
This works with no issues on test sites that I created. However, when trying to implement this in a legacy Web Forms site, I'm running into something that I don't understand.
When I log into one site, the test code on the second site has the following results:
var cookie = FormsAuthentication.GetAuthCookie("username", false); // works
var ft = FormsAuthentication.Decrypt(cookie.Value); // returns correct info
var isAuthentication = User.Identity.IsAuthenticated; // false
As an added bonus, whenever I sign in one site the other gets signed out (happens both ways.)
There must be something fundamental that I'm missing here.
Why is User.Identity.IsAuthenticated set to false, even though the FormsAuthentication ticket seems to be decrypting with no issues?
UPDATE: as pointed out below - FormsAuthentication.GetAuthCookie is not a valid way to obtain the existing auth cookie. Which brings me to this: I can see the top domain cookie in the browser, but it doesn't show up in the request. I suspect this is where the problem is occuring.
Solution: Both sites were not targeting the same version of .Net Framework as specified by the web.config:
<httpRuntime targetFramework="4.5" />
<compilation debug="true" targetFramework="4.5" />
Updating both sites to target the same framework fixed the problem.
The GetAuthCookie creates a new cookie:
http://msdn.microsoft.com/en-us/library/vstudio/3fay1e4k(v=vs.100).aspx
Creates an authentication cookie for a given user name. This does not set the cookie as part of the outgoing response, so that an application can have more control over how the cookie is issued.
No wonder it works, it doesn't look into the existing cookie.
My theory is that you have your new sites on an x64 machine and the legacy website sits in an x86. The encryption differs in such scenario even if keys are the same. Another possible reason is a different version of .net as the encryption algorithm has been changed in .net 4.

Why is ASP.NET ignoring my Membership connection string?

I have an ASP.NET app using built-in Membership functionality. As such, I have a connection string in my web.config that looks like this:
<add name="MembershipSqlServer" connectionString="Data Source=servername;Database=aspnetdb;uid=user;pwd=password;" />
When working on my dev machine, everything is peachy keen. But when I move things to the web server (which also happens to run the SQL Server), I get this error when User.IsInRole() is called:
System.Data.SqlClient.SqlException: Login failed for user 'NT AUTHORITY\NETWORK SERVICE'.
F$%*&!! Why is it attempting to connect in this way? Why isn't it using user/password from the connection string? Web.config is identical on dev and server, I am using the DB on the server during development.
OK, I figured it out... only 35 minutes. :P
Long story short: There are two parts to asp.net membership… a membership provider and a ROLE provider. Why you’d ever want these two things separated, I don’t know… But my web.config wasn’t specifying the role provider and connection string, so it was defaulting to the settings in machine.config (aka LocalSqlServer connection string).
So all this time, my app users were on the server... but the roles were stored in a local .MDF file in App_Data. Ugh.
What does the membership providers section in your web.config look like? Is it possible that you left out the connectionStringName attribute? In which case, I believe it would be trying to connect to the database on your local machine using integrated security.
The membership providers section in your web.config should look something like:
<membership defaultProvider="SqlProvider">
<providers>
<add
name="SqlProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="MembershipSqlServer"
...
/>
</providers>
</membership>
Do you see this <authentication mode="Windows" /> in your web.config? And your other connectionString uses Integrated Security=True; On your Sql server in order to use windows authentication you must have a Login(on the server) for the windows user or group as well as have an associated user in the database.
The simple but not suggested fix would be to create a login for 'NT AUTHORITY\NETWORK SERVICE'
on you sql server and then a user in your specific database for that maps to that login.
The secure way is to do this for each of the network security groups that need to access the sql server so you can manage the group permissions independently.
i think the answer is that :
public static string ConnectionString(SPSite site)
{
var connectionStringField = BaseMembershipProvider(site).GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
if (connectionStringField != null)
{
return connectionStringField.GetValue(BaseMembershipProvider(site)).ToString();
}
else
{
return "";
}
}
it worked for me with out any Error
thanks babania

Resources