ASP.NET Combining Windows Authentication with Custom Application Groups/Roles - c#

Full disclosure, I do not fully understand the world of windows auth, active directory and LDAP and have had little experience with individual user accounts via sql server. Additionally, I have found that most documentation on the web, especially those put forth by Microsoft, assume that you develop in a pure Microsoft world and have the ability to implement the most current and any which solution, framework, or service they provide.
I am developing an intranet application. And for various reasons I cannot take advantage of Active Directory groups/roles but would like to mimic this functionality as much as possible. Thus I need to be able to manage users/roles/groups internally within the application. However, I would like the ability to be able to detect a users Windows Auth credentials in the process. In other words I do not want the user to have to register nor do I want them to have to log in but rather use the windows account they are signed in as.
The application managed roles would then determine various functionality the user will have within the application.
This is an asp.net MVC application. I will ultimately need to satisfy the following requirements in regards to authorization.
1) Compare current windows user with application user store and roles. Can use SQL server this.
2) Manipulate functionality based on a users role
3) allow for an admin to search AD and add a domain\User to the store as well as assign groups
4) Create Groups and register with application components
Any information as to how I could address on or all of these would be vastly beneficial.

What you are looking for is a custom role provider.
It is extremely easy and simple to do. Simply create a class that inherits from System.Web.Security.RoleProvider. The only methods you need to implement are IsUserInRole and GetRolesForUser.
You may just throw a NotImplementedException on all the other methods.
Then, tie it to your application in Web.Config by setting the roleManager element under System.Web.
public class CustomRoleProvider : RoleProvider
{
private mydatabase db;
public override string ApplicationName { get; set; }
public CustomRoleProvider()
{
db = new mydatabase();
}
public override bool IsUserInRole(string username, string roleName)
{
//This will return the user object.
//To get the username of the logged on user, you can use User.Identity.Name
//To remove the domain name from the username: User.Identity.Name.Split('\\').Last();
var user = db.CurrentUser();
return user.Roles != null
&& user.Roles.Count > 0
&& (user.Roles.Exists(x => x.Roles.RoleNm == roleName));
}
public override string[] GetRolesForUser(string username)
{
var user = db.CurrentUser();
return user.Roles.Select(x => x.Roles.RoleNm).ToArray();
}
#region not implemented
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override void CreateRole(string roleName)
{
throw new NotImplementedException();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
throw new NotImplementedException();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
throw new NotImplementedException();
}
public override string[] GetAllRoles()
{
throw new NotImplementedException();
}
public override string[] GetUsersInRole(string roleName)
{
throw new NotImplementedException();
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override bool RoleExists(string roleName)
{
throw new NotImplementedException();
}
#endregion
}
and then, in Web.Config
<system.web>
<roleManager defaultProvider="CustomRoleProvider" enabled="true">
<providers>
<clear />
<add name="CustomRoleProvider" type="ThisProject.CustomRoleProvider, ThisProject" />
</providers>
</roleManager>
</system.web>
DISCLAIMER: There are likely typos in this code but you should be able to get the gyst

There is a membership provider created specifically for ActiveDirectory:
https://msdn.microsoft.com/en-us/library/system.web.security.activedirectorymembershipprovider(v=vs.110).aspx.
You can implement that provider in your app. In addition, you can create your own membership provider if you need additional functionality that the ActiveDirectoryMembershipProvider does not provide:
https://msdn.microsoft.com/en-us/library/f1kyba5e.aspx

Asp.Net Identity separates Identity and Authorization as two distinct components.
By design you can choose to use the AD identity piece with the Asp.Net Authorization piece. Such that you can use the local AD token to identify WHO the user is and then use that token to assign them privileges (roles and/or claims) based on that identity. Similar to how you can also use Google, Facebook or Twitter identities. Obviously, if your AD authorities won't allow you to query AD for "who is the user behind token X" then this answer is moot.
I haven't time to go further with this right now, but I think this should start you in the right direction.
(caveat: you MAY become limited to using a Microsoft browser. Last I looked only IE would send the Active Directory Token with the HttpRequest IF the request was being sent to a local domain server (aka the 'intranet' zone). I have heard that Chrome will allow you to configure it do this as well, but have never actually done it.)

Related

Custom RoleProvider with Windows Authentication in Web API

I have a WebAPI project in which I need to use Windows Authentication for the Users but implement a Custom Role Provider for the Authorize attributes above the routes. When I implement it however I always get {"Message":"Authorization has been denied for this request."} as the result for my call. Furthermore none of my breakpoints ever trigger except for the one in the constructor of the Custom Role Provider
Controller
[Authorize(Roles="Administrator")]
[RoutePrefix("api/Constituents")]
public class ConstituentsController : ApiController
{
[Route("Constituents")]
[HttpGet]
public IDataResponse<List<IConstituent>> GetConstituents()
{
return service.GetConstituent();
}
Custom Role Provider
public class CustomRoleProvider : RoleProvider
{
public CustomRoleProvider()
{
this.UserRepo = new UserRepository(); //Breakpoint here triggers
this.RoleRepo = new RoleRepository();
}
public override string[] GetRolesForUser(string username)
{
var roles = UserRepo.GetUser(username)?.Roles?.Select(r => r.Name).ToArray();
return roles;
}
public override bool IsUserInRole(string username, string roleName)
{
var user = UserRepo.GetUser(username);
return user.Roles.Select(r => r.Name).Contains(roleName);
}
Web Config
<authentication mode="Windows"/>
<roleManager cacheRolesInCookie="false"
defaultProvider="CustomRoleProvider"
enabled="true">
<providers>
<clear />
<add name="CustomRoleProvider"
type="Data.CustomRoleProvider, Data" />
</providers>
What piece of the puzzle am I missing? I need to get the current user making the request and then check to see if the have the appropriate role in the database.
Thanks
By default, Visual Studio sets the Windows Authentication property on your project to Disabled. In the Properties pane (not the tab), you need to flip the property to Enabled. That should let you hit the breakpoints on your RoleProvider.
When you put your application on a server, you may have to perform a similar process in IIS to Enable Windows Authentication and Disable Anonymous Authentication.

Is a Role Provider implementation necessary?

I am struggling a little bit to completely understand what is the correct way to implement a windows authentication and role based authorization scheme into an MVC4 application. When the user accesses the (intranet) website I currently have the following method check the user name against the against a database table
List<string> permissionList =
PermissionBo.GetUserPermissionsList(PermissionBo.ParseUserName(User.Identity.Name));
Permissions permissions = new Permissions(permissionList);
Then the following if state adds a role to a user object:
if (permissions.IsAdmin)
{
if(!Roles.RoleExists("UtilitiesToolAdmin"))
{
Roles.CreateRole("UtilitiesToolAdmin");
}
if(!Roles.IsUserInRole(User.Identity.Name, "UtilitiesToolAdmin"))
{
Roles.AddUsersToRole(new string[] { User.Identity.Name }, "UtilitiesToolAdmin");
}
}
I feel like this may be an incorrect way to go about implementing this, but I am not sure where I am going wrong. Is this sufficient to begin using the authorize attribute like so:
[Authorize(Roles="UtilitiesToolAdmin")]
public static void Foo()
{
return "Bar"
}
If not what am I missing?
If all you are doing is simple role checking, a custom Role Provider might be a bit of an overkill (Role Providers also provide facilities for managing the roles themselves). What you will end up with is a class full of
throw new NotImplementedException();
Instead, consider creating a custom user principal. The IPrincipal interface defines an IsInRole method that returns a bool. This is where you would put your custom role checks. The advantage of the custom user principal is that now all of the built in ASP.NET role-checking goodies should "just work" as long as you replace the default user principal object with your custom one early enough in the lifecycle.
This SO answer has one of the best examples I've seen of using a custom user principal with an MVC application.

Converting Word to PDF using Interop requires admin rights

First off, I know that this falls into the category of "not recommended practice", but I have a requirement to convert Word documents to PDF, via an ASP.Net site, and I have been using the Word Interop as it is free, simple to implement and Word is already installed on the server.
When I have been testing, it is working for me but after publishing users are reporting "Unauthorised access" errors. I have admin rights to the server and I found that adding the users as admin on the server works but I do not want to grant admin rights to every user.
So one of a number of things needs to happen, is there an alternative, free, library for converting a Word document to PDF that I could be using? Is there an easy way to get my users access to the Interop library without giving admin rights? Is there a way to impersonate an admin user for this part of the web application, seeing as the application requires Windows Authentication?
Is there anything else I have not thought of that could benefit me?
You can use impersonation to run code as a different user. There is a good impersonation class available on CodeProject here, http://www.codeproject.com/Articles/10090/A-small-C-Class-for-impersonating-a-User.
Just create a new admin account on the machine with access to do what you want, store the credentials in the web.config in app settings, (encrypt them if you want to with rijandael and decrypt them with code, etc.
But long store short, you use it like this,
using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
{
//This code is running elevated as the specified user
}
//This code is reverted back to the previous user.
I actually wrote my own web.config ConfigurationElement for storing credentials. It looks like this,
public class CredentialConfigurationElement : ConfigurationElement
{
#region Properties
[ConfigurationProperty("userName", DefaultValue="", IsRequired=true)]
public string UserName
{
get { return (string)this["userName"];}
set { this["userName"] = value; }
}
[ConfigurationProperty("password", DefaultValue = "", IsRequired = true)]
public string Password
{
get { return (string)this["password"]; }
set { this["password"] = value; }
}
#endregion
#region Explicit Operators
public static implicit operator NetworkCredential(CredentialConfigurationElement value)
{
return new NetworkCredential(value.UserName, value.Password);
}
public static implicit operator CredentialConfigurationElement(NetworkCredential value)
{
return new CredentialConfigurationElement() { UserName = value.UserName, Password = value.Password };
}
#endregion
}
However, to use it, you need to make a Custom ConfigurationSection, a class that inherits from ConfigurationSection and exposes the CredentialConfigurationElement as a property.
E.g. you could make a new section called CodeImpersonatorSection : ConfigurationSection
And in there expose the CredentialConfigurationElement as a property called ImpersonationCredentials.
Then use (CodeImpersonatorSection)WebConfigurationManager.GetSection("/yoursectionnamehere"); to get an instance to the configuration.
Optionally modify the Impersonator class to do that automatically, and change it to have a static method like
Impersonator.RunUnderImpersonation(p => {
//This code is impersonating the configured user.
});

How do I secure a web app using active directory groups?

Sorry for the basic question, first time with Web MVC4 in C#...
I'm creating a web interface for an application I've written in C#/SQL. I've been able to attach the MVC4 framework to the SQL DB. Now I want to secure what people can do based on group membership in AD. I have the authentication in my web.config set to "Windows" and it properly displays the User.Identity.Name that i'm logged in with. So I know it's pulling up the current logged in user. More over, I need to be able to authenticate a user outside of the active directory domain in the case of an android or ipad device. I haven't gotten that far yet though... for the most part, I'd like to auto authenticate the logged in user if possible then prompt for a username/password if none exists.
Ok, also I already know how to pull group membership for a user in AD. But I need to run that AD query and store that information somewhere that can be accessed on each page. Then on each page how do I access that variable?
For example, I don't want to display a menu option if they don't have access to it so that variable needs to be used to either display or not display the menu option that's being secured. Also, I assume I need to add that security on the webpage as well so that if someone tries to go there manually they cannot.
I assume I don't want to use session variables for security reasons..
In the past with Adobe Flex I used a singleton to manage the session state. I did a search out there and people are saying that it's probably not a good idea in C#. Not many examples of this anyway...
What are you doing to do this?
Here is what I would recommend. Start looking for examples of the ActiveDirectoryMembershipProvider Class. This MembershipProvider combined with Forms Authentication will provide you with a secure system to authenticate users.
Once authenticated, you need to authorize your users to access resources by combining the Active Directory Role Provider(ADRP) (to determine User Groups) with the standard way of Securing your MVC Application.
To get you started I created these simple extension methods when you can extend to use the ADRP (as I haven't used the ADRP).
public static class IPrincipalExtensions
{
private static _adminName = "Administrator";
public static bool IsAnonymous(this IPrincipal instance)
{
return (instance == null);
}
public static bool IsAdminOrInRole(this IPrincipal instance, string role)
{
if (instance == null
|| instance.Identity == null
|| !instance.Identity.IsAuthenticated)
{
return false;
}
bool result = instance.IsInRole(role)
|| instance.IsInRole(IPrincipalExtensions._adminName));
return result;
}
}
Then I also extended the default AuthorizeAttibute to give me an attribute I can use solely for Administrators:
public class AuthorizeAdministratorAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
bool result = false;
IPrincipal user = httpContext.User;
if (user.Identity.IsAuthenticated)
{
result = user.IsAdmin();
}
return result;
}
}
This uses the same extension methods provided in my IPrincipalExtensions so I don't repeat myself. Some might find this overkill as the following two lines are equal:
[Authorize("Administrator")]
[AuthorizeAdministrator]
However, since the first example is using a string, a simple mistype denies access, and if I decided to change the role/group name to "Admins" it becomes more difficult. So using the second one (which I could argue is strongly typed) if the group changes, I only have to change the name in one location.

IsUserInRole calls GetRolesForUser?

When I implement the RoleProvider class and call Roles.IsUserInRole(string username, string roleName), code execution first goes to the method 'GetRolesForUser(string username)'. Why is this? I don't want to iterate all roles when I am just looking for the single value of whether that user belongs in one role. Is this a limitation of .NET's role provider class or is there something I can do to control the execution of code a bit more?
Here's the calling code
if (Roles.IsUserInRole(CurrentUser.UserName, "Teacher")) {
And here's the implementation of IsUserInRole
public override bool IsUserInRole(string username, string roleName) { return true; }
But the code GetRolesForUser always gets implemented first:
public override string[] GetRolesForUser(string username) {
string[] roles = GetAllRoles();
List<string> userRoles = new List<string>();
foreach (string role in roles) {
if (IsUserInRole(username, role)) {
userRoles.Add(role);
}
}
return userRoles.ToArray();
}
The RoleProvider.IsUserInRole(username, password) is used for checking roles for a given user which is not the current loggon user(for current logon user, it also use the Principal.IsInRole instead). And for RolePrincipal, it always use the GetRolesForUser to cache the roles and do the role checking among the cached role list. (source)
can user Roles.Provider.IsUserInRole instead of Roles.IsUserInRole
There's a layer Microsoft's role provider solution that enables caching a user's roles in a cookie so it doesn't need to call the provider's GetRolesForUser method. I believe the cookie caching is part of the Roles class, so as long as you implement from the RoleProvider base class, it should be compatible. It's worth a look at the code in reflector to get an idea of how MS implements their own abstract classes, and what the static helper classes do (Roles and Membership)
Try adding cacheRolesInCookie="true" to the roleManager element in your config file, and see if the flow changes.
Since you're using your own implementation of a RoleProvider, you can also override the IsUserInRole method and provide your own implementation of checking if a user is in a role.
UPDATE: This block of code gets called inside the Roles.IsUserInRole method:
IPrincipal currentUser = GetCurrentUser();
if (((currentUser != null) && (currentUser is RolePrincipal)) && ((((RolePrincipal) currentUser).ProviderName == Provider.Name) && StringUtil.EqualsIgnoreCase(username, currentUser.Identity.Name)))
{
flag = currentUser.IsInRole(roleName);
}
else
{
flag = Provider.IsUserInRole(username, roleName);
}
The else block is what will call your custom provider's IsUserInRole method.
So the roles for your user have not yet been added to the Principal object. If you just haven't gotten around to that step yet, OK. If not, make sure you do that. It will make sure that every time you call Roles.IsUserInRole, or User.IsInRole that those functions will use an in-memory cache of the roles for the user (once loaded) instead of having to go to the database every time. (Although the base role provider and Roles manager class should take care of this for you.)
Can you verify the config file settings for the role provider? Also, what version of .net are you using? Are you manually managing the login process or are you using the .net login control? Have you implemented a custom Roles class? Or are you using the System.Web.Security.Roles?

Categories

Resources