ASP .NET MVC 4 AuthorizeAttribute and ActiveDirectoryMembershipProvider - asp.net

I'm trying to implement Active Directory role based authorization in my ASP.NET MVC 4 Intranet Web Application by using the AuthorizeAttribute.
[Authorize(Roles = "CONTOSO\\G_Helpdesk")]
public ActionResult Index() {
return View();
}
This should only allow members of the group CONTOSO\G_Helpdesk be able to view the index page. My development Virtual Machine and Web Server are both NOT domain members. So I thought I should configure a ActiveDirectoryMembershipProvider. My web.config is shown below.
<authentication mode="Windows"/>
<authorization>
<deny users="?"/>
</authorization>
<membership defaultProvider="ADMembershipProvider">
<providers>
<add name="ADMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADService"
connectionUsername="CONTOSO\User"
connectionPassword="myPassword"
attributeMapUsername="sAMAccountName"/>
</providers>
</membership>
<connectionStrings>
<add name="ADService" connectionString="LDAP://dc1/DC=CONTOSO,DC=com" />
</connectionStrings>
Now here's the problem. I'm not able to login using an active directory user who is member of the CONTOSO\G_Helpdesk group. In fact I'm not able to login at all. When I change the attribute to the lines below, I am able to login using a local account.
[Authorize]
public ActionResult Index() {
return View();
}
Does the AuthorizeAttribute even use the membership provider? I think it totally ignores it.

I found out I'm not thinking in the right direction. I won't be able to authenticate from a standalone webserver using AD credentials while using Windows Authentication. Source.
I have two options.
Make the webserver member of the Domain.
Use Forms authentication.
I hope this answer will save people who are struggling with the same problem some time.

Related

Basic authentication with ActiveDirectoryMembershipProvider

I'm trying to get users in the local domain authenticated from ActiveDirectory by iis/asp.net application hosted on a non-domain host.
This is the set up
local domain = MYDOMAIN
iis host = 10.10.1.1 (not in MYDOMAIN)
ActiveDirectory LDAP connection string = LDAP://10.20.1.1/DC=MYDOMAIN,DC=local
web.config
<connectionStrings>
<add name="ADConnectionString" connectionString="LDAP://10.20.1.1/DC=MYDOMAIN,DC=local" />
</connectionStrings>
...
<authorization>
<allow users="*"/>
<deny users="?"/>
</authorization>
<membership defaultProvider="ADMembershipProvider">
<providers>
<add
name="ADMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="MYDOMAIN\Administrator"
connectionPassword="password"
/>
</providers>
</membership>
iis settings
Basic Authentication enabled
All other Authentication methods disabled
But with this set up users are not authenticated when credentials entered on the browser auth prompt (user name entered as MYDOMAIN\user - no change without the MYDOMAIN\ part). I don't see iis even connecting to the AD server (10.20.1.1)
What am I doing wrong and how can I debug an issue like this?
Basic Authentication in IIS has no knowledge of Membership Providers in ASP.NET. If you want to do that, then you need to write a custom basic authentication module that uses the Mebership APIs (ValidateUser, RoleProvider and such). Luckily it is extremely easy to do that, and we wrote a sample several years ago on how to do that, see this for the end to end code and configuration of it (do note that the call to membership is commented out in the sample, but you can just uncomment the line) :
http://www.iis.net/learn/develop/runtime-extensibility/developing-a-module-using-net

"Trust relationship between ... and the primary domain failed" in MVC5 Authentication

I have a ASP .NET MVC5 application in which I am not using Windows Authentication.
Everything was working fine until I tried running the application outside of the Domain in which it was being developed and (for whatever reason) got a:
The trust relationship between this workstation and the primary domain failed.
when I'm trying to do User.IsInRole("Admin").
I am using custom Identity, Role, IdentityStore, RoleStore, etc. from .NET's Identity and I can see that the User and Role data is being retrieved from the (MongoDB) database correctly.
There are plenty of questions regarding this issue, but they're from people who want to use Windows Auth. and impersonation in their MVC applications:
With windows authentication, The trust relationship between the primary domain and the trusted domain failed, when calling IsInRole
How to configure Windows Authentication / Impersonation + IIS 7 + MVC
The trust relationship between the primary domain and the trusted domain failed
My.User.IsInRole("Role Name") throws a Trust Relationship error on Windows 7
So why exactly am I getting this SystemException if I'm not using Active Directory and (as far as I know) not doing anything that might depend on the PC's domain? Am I missing some configuration (either in my Web.config or IIS Express)?
EDIT:
Ok, so narrowing it down a bit...
My User.IsInRole("Admin") line is inside an if() statement in my _Layout.cshtml View (i.e., to know what to show in the nav. bar depending on the role).
I now know I only get the error above when no user is authenticated and I'm not in the domain I used for dev. If I place a breakpoint on that line, I can see that the User object is is a System.Security.Principal.WindowsIdentity and its underlying Identity is System.Security.Principal.WindowsIdentity.
On the other hand, if the user is authenticated, then the User object and ts Identity are System.Security.Claims.ClaimsPrincipal and System.Security.Claims.ClaimsIdentity.
Why is it using Windows Identity at all (when unauthenticated) and how can I disable it?
So, based on my EDIT, I've modified my _Layout.cshtml so that instead of having
#if(User.IsInRole("Admin")) {...}
I have
#if(User.Identity.IsAuthenticated && User.IsInRole("Admin")) {...}
which seems to solve the problem.
I believe the problem was that ASP .NET Identity uses an empty WindowsIdentity when no user is authenticated and when I try to check for the User.IsInRole, then it will try to check the roles of a WindowsIdentity against an Active Directory that I don't have. Obviously I should first check if the user is even logged in before attempting to check its roles, so mea culpa.
But, even though the change above seems to fix my code, I'd be very interested in knowing more about this behavior: why is it using an empty System.Security.Principal.WindowsIdentity when no user is authenticated. I'll accept any answer which explains that.
I've had this issue - It failed for me if I tested an active directory group that didn't exist.
Make sure you're using a group that exists!
I was having this issue with Asp.Net Core 3.1 with Windows Authentication, but this thread came up first when searching the internet. I ended up resolving the issue by decorating the controller class declaration with the following:
using Microsoft.AspNetCore.Authorization;
[Authorize]
public class SetupController : Controller
Hope this is helpful for someone that is using Windows Authentication and is having the same error.
We were having this same issue on a new production server. Using the Identity Framework and restricting access to a specific directory with a web.config file denying any unauthenticated users. When unauthenticated users tried to access a page in this directory that contained any User.IsInRole("RoleName") code, they were getting the "Trust relationship..." error.
None of the fixes mentioned in other SO answers worked for us.
Turns out we just had to enable Forms Authentication in IIS - problem solved.
The "trust relationship between the primary domain and the workstation has failed" error message usaully requires that the computer be removed from the domain and then rejoined. Now there are a few ways to do this. As included in the link above, are instructions on how to do so either on the computer displaying the error or remotely. You can also do so in Active Directory and in PowerShell.
<authorization>
<allow roles="pri\Domain Users" users="pri\domain_user" />
<deny users="?" />
</authorization>
make sure that you have the above line in your web.config file and complete the user field with the correct user name.
I've just resolved this in our systems, unfortunately, none of the other suggestions worked for me. The issue was caused by an orphaned SID in a network folder the code was attempting to access. Once removed it started working again.
I had exactly the same scenario with custom Authentication Module and the same error when doing IsInRole. The highest ranking solution (User.Identity.IsAuthenticated && ...) did NOT help. So, I played quite a bit with it. Finally I found that I had to remove a (preCondition="managedHandler") attribute from my module declaration in web.config file. So, instead of:
<system.webServer>
...
<modules>
...
<add name="CompanyAuthentication" type="Company.Authentication.AuthHttpHandler" preCondition="managedHandler" />
</modules>
I would have to have:
<system.webServer>
...
<modules>
...
<add name="CompanyAuthentication" type="Company.Authentication.AuthHttpHandler" />
</modules>
That did the trick for me!
For me, the whole membership provider configuration tags were missing. After i copy those from one our previous apps, it worked fine.
<system.web>
<authentication mode="Windows" />
<compilation debug="true" targetFramework="4.7.1" />
<httpRuntime targetFramework="4.7.1" />
<httpModules>
<add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
</httpModules>
<profile defaultProvider="DefaultProfileProvider">
<providers>
<add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
</providers>
</profile>
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<roleManager defaultProvider="CustomRoleProvider" enabled="true" cacheRolesInCookie="false">
<providers>
<add name="CustomRoleProvider" type="ABC.ABCModels.ABCRoleProvider" />
</providers>
</roleManager>
<sessionState mode="InProc" customProvider="DefaultSessionProvider">
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
</providers>
</sessionState>
On my case, I am not using User.Identity but rather Thread.CurrentPrincipal.Identity.Name
So when I approach this line of code:
Thread.CurrentPrincipal.IsInRole("admin");
That's where I will encounter the same error message of:
The trust relationship between this workstation and the primary domain failed.
There are two cases why I encountered the same issue and of course the fixes I made:
I was disconnected with my VPN. This will look for the role that doesn't exist since I am not connected with my VPN and not connecting with my AD accounts.
If I am connected with my VPN and the role admin doesn't exist based on my code above, it will certainly trigger the same error message.
I think it is worth sharing how I've fixed on my situation as these answers helped me to figure it out.
We were allowing/restricting access to pages via web.config and this was happening before touching any code (so it didn't matter where the breakpoint was, the error was coming).
Since this was not amazing too, I've decided to implement this validation in the code manually instead of using the web.config one.
our web.config used to look like this:
<configuration>
<system.web>
<authorization>
<allow roles="10,5"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
We've changed to:
Created a page (called restricted page) that inherits from Page
This page has a protected variable called RequiredAccessLevel
The OnLoad of the page we check if the user is in the role of the RequiredAccessLevel and if it is not, we redirect to a custom AccessDenied page
Made the pages that should have this restriction inherit from RestrictedPage instead of Page
On the constructor of the pages that are inheriting from RestrictedPage we set the access level necessary
Like this:
RestrictedPage.cs
public class RestrictedPage : Page
{
protected int[] RequiredAccessLevel { get; set; } = { };
protected override void OnLoad(EventArgs e)
{
if (RequiredAccessLevel.Length > 0)
{
var allowed = false;
foreach (var ral in RequiredAccessLevel)
{
if (Page.User.IsInRole(ral.ToString()))
{
allowed = true;
break;
}
}
if (!allowed)
{
Response.Redirect("~/AccessDenied.aspx");
}
}
base.OnLoad(e);
}
}
Example.cs
public partial class Example: RestrictedPage
{
public Example()
{
RequiredAccessLevel = new[] {10};
}
}

Forms Authenticated web application with Basic Auth on a specific folder/path

I have a Forms Authenticated web application but I need Basic Authentication on a couple of services which are all located at a specific path (ie. "~/Services/").
I originally tried to add a tag in the web.config with a seperate custom MembershipProvider for the path like so:
<location path="Services">
<system.web>
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
<membership defaultProvider="ServicesMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="Company.WebProject.DeviceMembershipProvider" connectionStringName="DefaultConnectionString" applicationName="/" />
</providers>
</membership>
<httpModules>
<add name="BasicAuthentication" type="Company.WebProject.BasicAuthenticationModule" />
</httpModules>
</system.web>
</location>
But this was throwing errors:
It is an error to use a section registered as allowDefinition= 'MachineToApplication' beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.
So I realised that I wasn't allowed to use the authentication element in a location element.
After reading this article, I then tried hooking into the FormsAuthentication_OnAuthenticate method in the Global.asax. As I need to use Basic Authentication, I tried returning a 401 to prompt the browser for basic auth credentials. Unfortunately, it seems this was causing a redirect to the Forms Authentication log on page (ie. loginUrl).
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs e)
{
string path = VirtualPathUtility.ToAppRelative(e.Context.Request.Path);
if (path.Contains("/Services/"))
{
e.Context.Response.StatusCode = 401;
e.Context.Response.AddHeader("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", "CompanyRealm"));
e.Context.Response.End();
}
}
So now I have run out of ideas as to how to implement Basic Auth on a folder in a Forms Authenticated web application.
Does anyone have any idea how to achieve this?
You can't mix Forms Authentication with Windows Authentication in ASP.NET. You will need to create either a separate application for the two or you will need to implement Forms Authentication and Roles to properly do tiered access.
A bit late in the day, but I've posted some code here:
Combining Forms Authentication and Basic Authentication
Basically you just need to replace
e.Context.Response.End();
with
e.Context.Response.Flush();
e.Context.Response.Close();
Closing the Response object seems to prevent ASP from overriding the redirect. See the above link for full code.

ASP.NET Active Directory Membership Provider and SQL Profile Provider

I am currently designing a Membership/Profile scheme for a new project I am working on and I was hoping to get some input from others.
The project is a ASP.NET web application and due to the short time frame, I am trying to use any and all built in .NET framework components I can. The site will probably entertain < 5000 users. Each user will have a profile where custom settings and objects will be persisted between visits.
I am required to use an existing Active Directory for authentication. Since the AD schema cannot be extended to hold new fields, I am required to hold user settings and objects in a different data store. I have also been told ADAM is probably not a possible solution.
I was hoping to use the Active Directory Membership Provider for my authentication scheme and the SQL Profile Provider as a user profile data store. I would prefer not to build a custom profile provider, but I do not see this posing much of a problem if need be.
I was wondering if this is even a possible solution, and if so, has anyone had any luck with this approach.
Any comments would be greatly appreciated.
Thanks.
First off - I've never done this myself.
There's a really excellent series (14 !! parts) on the whole topic of ASP.NET 2.0 membership, roles and profile provider systems by Scott Mitchell at 4 Guys from Rolla.
According to my understanding, you should be able to configure this behavior you are looking for by using basically these two sections in your web.config:
<!-- configure Active Directory membership provider -->
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
<providers>
<add name="AspNetActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider,
System.Web, Version=2.0.3600, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</membership>
<!-- configure SQL-based profile provider -->
<profile defaultProvider="SqlProvider">
<providers>
<add name="SqlProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="SqlProfileProviderConnection"
applicationName="YourApplication" />
</providers>
<!-- specify any additional properties to store in the profile -->
<properties>
<add name="ZipCode" />
<add name="CityAndState" />
</properties>
</profile>
I would think this ought to work :-)
In addition to this as replied by Marc :
<add name="AspNetActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider,
System.Web, Version=2.0.3600, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
you might also need to add
connectionStringName="ADService",
attributeMapUsername="sAMAccountName"
with corresponnding connection string
<connectionStrings>
<add name="ADService" connectionString="LDAP://ServerIP" />
</connectionStrings>
If you are using .net 4.0 then you will need to replace
Version=2.0.3600
with
Version=4.0.0.0
So finally ,
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
<providers>
<add name="AspNetActiveDirectoryMembershipProvider"
connectionStringName="ADService"
type="System.Web.Security.ActiveDirectoryMembershipProvider,
System.Web, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
attributeMapUsername="sAMAccountName"/>
</providers>
</membership>
and since it is set as default, it can be referenced as :
MembershipProvider provider = Membership.Provider;
I am using Visual Studio 2012 and tried to do as sugested, but an error is shown:
To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
So I discovered that a few changes should be done to the default login form on the VS2012 with MVC 4 and entity framework as follows:
on file "AccountController.cs"
on the "public ActionResult Login(LoginModel model, string returnUrl)"
Change the
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
for
if (ModelState.IsValid && Membership.Provider.ValidateUser(model.UserName, model.Password))
on the "public ActionResult LogOff()"
Change the
WebSecurity.Logout();
for
FormsAuthentication.SignOut();
and add the following: FormsAuthentication.SetAuthCookie(model.UserName, false);
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && Membership.Provider.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Thanks for the information, its helped alot. Also rather than Setting the default Provider with MembershipProvider provider = Membership.Provider; you can set it with in the membership tag.
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
I"ve also writen a small how to and a download to a Visual Studio Project and Source configured to use AspNetActiveDirectoryMembershipProvider.
ASP.NET Forms Based Authentication - using AspNetActiveDirectoryMembershipProvider

ASP.NET Membership - Which user is authenticated and which user is impersonated?

i'm a little confused while trying to find out how ActiveDirectory and ASP.NET Membership work... I've created a new MVC project and removed the AccountController / Views. I've changed the Web.Config so that it uses ActiveDirectory and automatically authenticates users based on their current Windows login:
Web.Config
<authentication mode="Windows">
<forms
name=".ADAuthCookie"
timeout="10" />
</authentication>
<membership defaultProvider="MyADMembershipProvider">
<providers>
<clear/>
<add
name="MyADMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="MYDOMAIN\myuser"
connectionPassword="xxx"
/>
</providers>
</membership>
This works nicely, as I can do the following to get the users username like this:
User.Idenity.Name() 'Gives MYDOMAIN\myuser
Looking at the following, actually makes me confused:
Threading.Thread.CurrentPrincipal.Identity.Name() 'Gives MYDOMAIN\myuser
1. Shouldn't the thread identity be IUSR_WORKSTATION or ASPNET_WP username?
2. What's the difference between Authentication and Impersonation?
myuser is the Authenticated user on that application, that's why your CurrentPrincipal is giving you MYDOMAIN/myuser. The application impersonates IUSR_WORKSTATION when it uses resources like the database, and is a completely different issue.
If you go to Project on your toolbar, and select ASP.NET Configuration, it will open a website that lets you access these settings and create users, roles etc.

Resources