So here's what I'm attempting to do:
I want to secure a particular virtual directory on an ASP.NET application using Windows Authentication. The virtual directory only has one file, default.aspx. In the code-behind, I simply want to get the Windows username of the user who logged in but no matter what property I use, it returns the security context of the application.
I need the site itself to run under the context of the "AppContext" user for file permissions, etc. However, to determine whether the user is authorized to access the site, I need to use that user's Windows domain credentials.
IIdentity windowsIdentity = WindowsIdentity.GetCurrent();
logger.Info("Windows username: " + windowsIdentity.Name);
logger.Info("System.Environment.Username: " + System.Environment.UserName);
logger.Info("HttpContext: " + HttpContext.Current.User.Identity.Name);
The output is:
Windows username: DOMAIN\AppContext
System.Environment.Username: AppContext
HttpContext:
Assuming I'm logging in as myself and my Windows username is "Scott", I'd expect HttpContext to return "DOMAIN\Scott", but it returns nothing. I do get the Windows authentication dialog when attempting to access the virtual directory and I can successfully authenticate as "Scott". In the IIS7 Authentication dialog, all authentication mechanisms are turned off except for "Windows Authentication".
The strange thing is that I have another site where it's working just fine and I can't figure out what the difference is. The only difference I can see is that the working site is an actual IIS website and the one I'm currently working on is a virtual directory.
The output fom the working site is below. This is what I'm trying to replicate on the virtual directory but I must be missing something.
Windows username: DOMAIN\Scott
System.Environment.Username: AppContext
HttpContext: DOMAIN\Scott
ADDITIONAL NOTES:
Neither site has an <identity> element in web.config.
<authentication mode="Windows" /> is set properly.
Both the virtual directory and the site have only Windows Authentication enabled in IIS.
The code is being executed in the Page_Init method.
The bottom line is, I simply need to let the site itself run under the security context of the "AppContext" user (which the AppPool is configured to run as) but I need to get the currently logged in user's name in the default.aspx.cs code behind.
You'll find it in System.Threading.Thread.CurrentPrincipal.Identity.Name.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(System.Threading.Thread.CurrentPrincipal.Identity.Name);
}
}
Add a Web.config inside folder and set <authentication mode="Windows"/>
Check your authorization settings.
If the resource is accessible by anonymous user - and no resource earlier in the request chain of the browser triggered an authentication - IIS will not authenticate the user.
So, you must have Windows authentication, and deny anonymous access, to see a user name in HttpContext.
Related
I have a website that is using Windows authentication that calls an API. The API has the Authorize attribute:
[Authorize(Roles = #"myrole.local\role")]
The API then makes a call to a method in my business layer that does this:
string [] directories = Directory.GetDirectories("\\\\server\\folder\\folder2");
When running locally this works as intended. When my IT ops guy checks the audit logs, he sees that my login has accessed that folder.
Now when I deploy my website to our development environment, it no longer works (we get an "access denied" error for that folder). So we looked into it and found that the user trying to access the folder was our server (something like server$) tried to access folder and failed.
So my question is: how do I access the folder with the current user that is logged in? I have looked this up here on Stack Overflow and I see impersonation comes up a lot. I have tried this example with no luck:
public static List<DirectoryName> GetDirectories(IPrincipal user)
{
string[] directories;
using (WindowsImpersonationContext impersonationContext = ((WindowsIdentity)user.Identity).Impersonate())
{
//Insert your code that runs under the security context of the authenticating user here.
directories = Directory.GetDirectories("\\\\server\\folder\\");
}
}
I am still denied access.
I have the user that is logged in though, so I know I can 'see' them, but the folder I am trying to access says it's the server.
Does anyone know how to get around this? Am I missing something?
P.S. I have created the website with IIS and have set both the API and website to use windows authentication. (anon = false;); but still it says the server is trying to access the folder.
P.P.S. In the web.config for the API, I have these tags as well:
<system.web>
<!-- ... -->
<authentication mode="Windows"/>
<authorization>
<allow roles="group.local\group" />
</authorization>
</system.web>
Edit: So we were able to access the folder path with the logged in user but only when going to the website on the server.
If we go to the website off our dev server we will then get an access denied error. Also there is no audit line when this fails.
Hopefully this edit provides more insight.
I have a MVC 5 application which uses OWIN and identity2.0 for authentication.
My application require mixed authentication:
for users not a domain lets say "dom1", they should see a form based page
for users on domain "dom1" they should be logged in via windows authentication.
I want to know how can I get the domain name of user without authentication, at the time when user first hit my Account/Login action.
I have used :
PrincipalContext pcontext = new PrincipalContext(ContextType.Domain);
var domainName = pcontext.ConnectedServer;
and
System.Security.Principal.WindowsIdentity context = System.Security.Principal.WindowsIdentity.GetCurrent();
var domainName = pcontext.Name;
BUT both of these shows domain name of machine where website is deployed and not of the client's domain.
Any help is much appreciated and please correct me if I am doing any blunder.
"without authentication"? You can't. The Windows authentication has to be completed before you can see the user's account.
This is because of how Windows authentication works:
The browser accesses the site anonymously.
IIS returns a 401 response
The browser responds by making the request again with the Windows credentials included
IIS verifies the credentials with the domain controllers
IIS passes the verified Windows credentials to your application.
The only time your application can see anything about the user's account is at step 5 - after the authentication is successfully complete.
The domain can be seen here
Environment.UserDomainName
Gets the network domain name associated with the current user.
Per the MSDN documentation:
The UserDomainName property first attempts to get the domain name component of the Windows account name for the current user. If that attempt fails, this property attempts to get the domain name associated with the user name provided by the UserName property. If that attempt fails because the host computer is not joined to a domain, then the host computer name is returned.
If you're not authenticated, you'll unfortunately see the host computer name. No way around this that I'm aware of.
You can also get the ip of the client's request here
HttpRequest.UserHostAddress
I have an ASP.Net MVC 5 application which is currently using individual authentication (account/login.cshtml page with no authentication/anonymous access) and OWIN. Works fine.
As this is an intranet app I want to allow the users to log in under their windows account, another users windows account or an application account(admin, special user etc. - these accounts have no associated domain account).
For the first option I wanted to display their windows username on the login screen and they can simply click the "ok" button to login. To get the username I modified the Visual Studio Project properties to disable anonymous authentication and enable windows authentication. Also modified the web.config and set the authentication mode to Forms. This causes "HTTP Error 404.15 - Not Found". This appears to be due to an authentication loop caused by OWIN with the following suggestions to fix:
Ensure Login controller methods allow anonymous access (seems to be this way by default).
or Modify Startup.auth, comment out the LoginPath property.
or Modify the web.config, add the appSetting "owin:AutomaticAppStartup" with value "false".
I opted for the LoginPath fix and this appears to work (as does web.config change) in that there are no errors and the login page displays with the windows username (retrieved using System.Threading.Thread.Currentprinciple.Identity.Name).
The problem is now that once the user has logged in the OwinContext has no user ( HttpContext.GetOwinContext().GetUserManager()).
Ideally I don't need IIS or OWIN doing any authentication as it's done by the app - but I need the initial request (for the account/login page) to include the Authenticate headers so I can get the windows user.
Firstly I would like to understand what causes the "HTTP Error 404.15" and fix.
Secondly, how do I get OWIN to work with the authentication change - I just need it to persist the user for controller authentication.
This is just a guess but I believe the error is caused by the misconfiguration you've described: you have set the authentication mode to "Forms" but set the project to use Windows Authentication. It can be confusing but Windows Authentication is not Forms Authentication. When you are using Forms Authentication the user provides the credentials in the form that is submitted, validated (including all anti-forgery goodness) against the user store (I believe you are using ASP.NET Identity which would be a default for "Individual Authentication" setting) and if the validation is successful a cookie to set is included in the response. This cookie is then used to authenticate further requests.
As confirmed by Katana documentation, there is no built-in middleware for Windows Authentication - Microsoft simply assumes that IIS should be used for that. Which effectively prevents us from easily combining Katana OWIN middleware providers with Windows authentication. Now, easily is the key word: we still can "hack" our way around it.
Unfortunately, it still will be a hack: I have not found a way to make the authentication "transparent" (as in "a user opens the login form and can enter both the AD account credentials or the individual account credentials and everything just works"). You will need to maintain the individual account record for every Windows user (as you would do with any external OWIN middleware, such as Google or Facebook). You can automate the account creation and association though and make it look transparent. You can add an "external provider" button for your Windows authentication.
Authenticating the user would look like (in a separate "AD Authentication" controller):
bool userWindowsAuthentication = Request.LogonUserIdentity.IsAuthenticated;
if (userWindowsAuthentication) {
var userStoreDatabaseContext = new ApplicationDbContext();
var userStore = new UserStore<UserModel>(userStoreDatabaseContext);
var userStoreManager = new UserManager<UserModel>(userStore);
var userWindowsLoginCredentials = GetWindowsLoginInfo();
var existingInternalUser = userStoreManager.FindAsync(userWindowsLoginCredentials.UserName)
if (existingInternalUser) {
// It means that the user already exists in the internal provider and here you simply authenticate and redirect to destination
} else {
// It means that the user does not exist. You can automatically create the internal user record here and associate the Windows user with the internal record.
}
} else {
// It means that user is not signed in using Windows authentication, so you either want to redirect back to the login page or restrict access or do something else
}
As you can see, it's "dirty". Another hack: you can have additional layer (separate application or a virtual application) that accepts only Windows authentication. This app can be your log-in resource. If the user is authenticated with Windows AD you can redirect them to the correct login page. You can go even further and add their login info in the redirect request header but if you do so - the header must be encrypted to ensure that Windows authentication cannot be faked and the only thing that should be able to decrypt and validate it should be your main application. Again, dirty, but works.
Details of my app:
- ASP.NET MVC 3
- Windows Authentication mode
- IIS 6
So, in the web.config i put the impersonate to false, but when i try get the current user with Environment.UserName the app keeps getting the NETWORK SERVICE user. Why?
To get the current user in an ASP.NET MVC application you should use the User.Identity.Name property:
public ActionResult Index()
{
var currentUser = User.Identity.Name;
return Content("current user: " + currentUser);
}
Environment.UserName gives you the account under which the application pool executes.
Also make sure that in IIS you have disabled anonymous authentication in the properties of the virtual directory and left only Windows authentication checked.
How can I implement following in ASP.NET MVC application:
user opens intranet website
user is silently authenticated if possible
if NTLM authentication didn't worked out, show login form to user
user indicate login password and select domain from list of predefined domains
user is authenticated in code using AD
I know how to implement 4 and 5 but cannot find info on how to combine NTLM and forms.
So that NTLM native login/password dialog is never shown - transparent authentication or nice looking login page.
How should work?
Should user be asked login and password?
Can her current credentials (domain username) be used without asking to enter login and password?
UPDATE for these, investigating same problem:
When I was asking this I was not fully understand how NTLM authentication works internally.
Important thing here to understand is that if user's browser doesn't support NTLM properly or if NTLM support is disabled by user - server will never get chance to work around this.
How Windows authentication is working:
Client send a regular HTTP request to server
Server responds with HTTP status 401 and indication that NTLM authentication must be used to access resources
Client send NTLM Type1 message
Server responds with NTLM Type2 message with challenge
Client send Type3 message with response to challenge
Server responds with actual content requested
As you see, browser not supporting NTLM will not go to step (3), instead user will be shown IIS generated Error 401 page.
If user doesn’t have credentials, after cancelling NTLM authentication popup dialog window browser will not continue to (3) as well.
So we have no chance to automatically redirect users to custom login page.
The only option here is to have a “gateway” page where we decide if user should support NTLM and if so, redirect to NTLM protected home page.
And if not, show login form and allow authentication by manually entering login and password.
Decision is usually made based on users’ IP address and/or host name either by matching IP ranges or by checking table of predefined IPs.
This article might get you pointed in the right direction. Basically you have two apps in two virtual directories under the same host name. One app uses Forms authentication, one uses Windows. The one using Windows authentication creates a valid form authentication cookie and redirects to the second virtual directory.
ASP.NET Mixed Mode Authentication
I have this exact setup in production, I setup my portal to use FormsAuth and wrote a function that takes the visitors IP to look up the user account that is logged in to that IP / PC. Using the name I find (eg. DOMAIN\user), I verify the domain matches my domain and that the user name / account is valid in my FormsAth provider using Membership.GetUser(<user>). If this call returns a match and the user IsApproved I create a FormsAuthenticationTicket & cookie for the user. I have 400+ people on the network and this works perfectly, the only computers that still login are (1. Users without accounts in my portal, 2. A few MAC/Linux users, 3. Mobile users who did not boot on the network and had Group Policy enable their Firewall to High).
The catch to this solution is that it requires impersonation of a domain admin account to query the users PC, and that you use unmanaged code netapi32.dll.
Here is the code I use (external function calls not provided, for brevity). I've tried to simplify this a bit, since have LOTS of external calls.
string account = String.Empty;
string domain = String.Empty;
string user = String.Empty;
ImpersonateUser iu = new ImpersonateUser(); //Helper that Enabled Impersonation
if (iu.impersonateValidUser(StringHelper.GetAppSetting("DomainAccount"), StringHelper.GetAppSetting("DomainName"), StringHelper.GetEncryptedAppSetting("DomainAccountPassword")))
{
NetWorkstationUserEnum nws = new NetWorkstationUserEnum(); //Wrapper for netapi32.dll (Tested on Vista, XP, Win2K, Win2K3, Win2K8)
string host = nws.DNSLookup(Request.UserHostAddress); // netapi32.dll requires a host name, not an IP address
string[] users = nws.ScanHost(host); // Gets the users/accounts logged in
if (nws.ScanHost(host).Length > 0)
{
string workstationaccount = string.Empty;
if (host.IndexOf('.') == -1) // Pick which account to use, I have 99.9% success with this logic (only time doesn't work is when you run a interactive process as a admin e.g. Run As <process>).
{
workstationaccount = String.Format("{0}\\{1}$",StringHelper.GetAppSetting("DomainName"), host).ToUpper();
}
else
{
workstationaccount = String.Format("{0}\\{1}$", StringHelper.GetAppSetting("DomainName"), host.Substring(0, host.IndexOf('.'))).ToUpperInvariant();
}
account = users[users.Length - 1].Equals(workstationaccount) ? users[0] : users[users.Length - 1];
domain = account.Substring(0, account.IndexOf("\\"));
user = account.Substring(account.IndexOf("\\") + 1,
account.Length - account.IndexOf("\\") - 1);
}
iu.undoImpersonation(); // Disable Impersonation
}
Now using the account we grabbed in the first function/process, we now try to verify and decide if we should show a login or auto-login the user.
MembershipUser membershipUser = Membership.GetUser(user);
if (membershipUser != null && membershipUser.IsApproved)
{
string userRoles = string.Empty; // Get all their roles
FormsAuthenticationUtil.RedirectFromLoginPage(user, userRoles, true); // Create FormsAuthTicket + Cookie +
}
I wrote a blog post about this a long time ago, here is a link to the wrapper for netapi32.dll and my Impersonation helper that I provided in the post Source Code Download
You cannot have both NTLM and FormsAuthentication in the same ASP.NET application. You will need two different applications in separate virtual directories.