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.
Related
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.)
I have setup a custom role provider implemented using the code below except it seems it's never being used, and the default provider is being used instead. When decorating the HomeController with the [Authorize(Roles = "Administrator")] attribute the CustomRoleProvider constructor is being called (I only included the constructor in to see whether the breakpoint would be hit) but none of the methods are being called. And then I am left with a HTTP Error 401.0 - Unauthorized page.
Aside from adding the necessary bits to the web.config I haven't done anything else to get Windows authentication to work. I assume it's working though because if I don't include <allow users="*"></allow> (obviously without the inclusion of the Authorize attribute) I get a 401.2.: Unauthorized: Logon failed due to server configuration error, so I assume I'm being authenticated.
I've cleared my browser cookies as per this SO post but that had no effect.
CustomerRoleProvider
public class CustomRoleProvider : RoleProvider
{
public CustomRoleProvider()
{
}
public override bool IsUserInRole(string username, string roleName)
{
bool isUserInRole = false;
// omitted for brevity
return isUserInRole;
}
public override string[] GetRolesForUser(string username)
{
string[] roles = null;
// omitted for brevity
return roles;
}
// omitted for brevity
}
web.config
<authentication mode="Windows">
</authentication>
<authorization>
<allow users="*"></allow>
<deny users="?"/>
</authorization>
<roleManager defaultProvider="CustomRoleProvider" enabled="true">
<providers>
<clear />
<add name="CustomRoleProvider" type="ProjectName.UI.Mvc.Code.Providers.CustomRoleProvider" />
</providers>
</roleManager>
Home Controller
[Authorize(Roles = "Administrator")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
This was caused from not having IIS Express set to use Windows Authentication. To fix I selected the project in Solution Explorer, opened the Properties window set Windows Authentication = Enabled and Anonymous Authentication = Disabled. Custom role provider now works.
I am building an intranet site where users will be on the corporate domain and have different permission levels. I'm currently using <authentication mode="Windows"/> to control site access, but it seems like I should be using ASP.NET Identity.
For example, say my application is a dashboard for each department in the organization. I want to create a single AD group called DashboardUsers and put everyone that can touch the site in this group.
I also want to restrict the views in the dashboard. For example, I only want the IT group to see their view, and the Finance folks see theirs, etc.
Question -- should I be using Windows Authentication to control access to the site, and then use ASP.NET Identity for user level permissions?
I have done something similar to this using only WindowsAuthentication. You can tag your actions with the Authorize Attribute:
[Authorize(Roles = #"DashboardUsers")]
As long is this user is a member of the DashboardUsers AD group they will get access to this action. It seems like MVC magic, but it really is that simple.
Unfortunately this approach will not allow you to overload an action for different Roles as the Authorize Attribute is not part of the method's signature. In your views, you would have to show different anchor tags based on the current users role.
ie:
[Authorize(Roles = #"DashboardUsers\Manager")]
public ActionResult IndexManagers()
{
..
}
or
[Authorize(Roles = #"DashboardUsers\Finance")]
public ActionResult IndexFinance()
{
..
}
EDIT AFTER COMMENT:
Since your Identity is coming from AD, you could use logic in your controller like:
if(User.IsInRole("Finance"))
{
..
}
else if(User.IsInRole("IT"))
{
..
}
And this will check which AD Group they belong too. I know it's not very elegant, but I can't imagine mixing Windows Auth with a custom identity and managing permissions in your own db would be elegant either.
I have run into this dilemma before and wound up creating a custom role provider that i used in conjunction with windows authentication. I'm not sure you need the OWIN middleware when authenticating against AD.
public class MyAwesomeRoleProvider : RoleProvider
{
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
// i talk to my database via entityframework in here to add a user to a role.
}
// override all the methods for your own role provider
}
config file
<system.web>
<authentication mode="Windows" />
<roleManager enabled="true" defaultProvider="MyAwesomeRoleManager">
<providers>
<clear />
<add name="MyAwesomeRoleManager" type="MyAwesomeNamespace.MyAwesomeRoleProvider" connectionStringName="MyAwesomeContext" applicationName="MyAwesomeApplication" />
</providers>
</roleManager>
</system.web>
Normally use authorization settings to interact with the roles.
<location path="Register.aspx">
<system.web>
<authorization>
<allow roles="Administrator"/>
<deny users="?"/>
</authorization>
</system.web>
</location>
But I would like to have this setup in the database in a table
tblAuthorization
-IdAuthorization (Identy(1,1))
-IdCompany=5
-IdRol=5 (Administrator”)
-Path=”Register.aspx”
Is there a setting for a class to do this? There is something like Profile, RoleProvider ..
<roleManager enabled="true" defaultProvider="MyAccessRolProvider">
<providers>
<clear />
<add name="MyAccessRolProvider" type="MyAccessRolProvider" connectionStringName="MyConnectionString" applicationName="/" />
<add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
</providers>
</roleManager>
Right now the only I think that can to implement a validation in the Page_Load event
And if it is invalid making a Redirect but I would do a little more "Professional"
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if(!ValidateAuthorization())
{
Response.Redirect("Login.aspx");
}
}
}
Could help me with examples?
Thank you very much in advance
Your best bet is to implement a custom RoleProvider - you seem to be heading in this direction as your question includes a configuration section with a custom RoleProvider (MyAccessRolProvider).
You need to specify the fully-qualified name of your RoleProvider, e.g. MyNamespace.MyAccessRoleProvider, MyAssembly.
MSDN describes how to do this, including a sample implementation.
With the understanding that you are taking about authorization at the page level you could add a HTTP module (IHttpModule) and use your module to do the authorization checking. If the authorization failed then you could redirect as appropriate.
Something like this might work:
public class MyAuthorizationModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthorizeRequest += (new EventHandler(AuthorizeRequest));
}
private void AuthorizeRequest(Object source, EventArgs e)
{
bool isAuthorized = //your code here to check page authorization
if (!isAuthorized)
{
var response = //will need to get current response
response.Redirect("Login.aspx", true);
}
}
}
Note: Don't forget you'll need to add the module to your web application.
There is no out of the box support for this as far as I know however I would suggest you implement something as follows:
Create a new class that derives from AuthorizeAttribute, add an ID property and override the "AuthorizeCore" method.
public class CustomAutorizeAttribute : AuthorizeAttribute
{
public Guid ActionId { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Check user has access to action with ID = ActionId
//implementation omitted
}
}
You can add a new database table to hold on the Actions and their associated ID values. You can either add the record to the table manually or write some code to do this using reflection (I would advise this if you have quite a few actions and are likely to add more in the future).
Once this is done you can assign users or application roles access to these actions.
In you custom Authorize attribute you have to check whether the user has access to the given action returning true or false accordingly from the AuthorizeCore method.
I would advise grabbing a collection of all actions a user has access to when a user logs in and storing them in either the server cache or some kind of distributed cache so that you aren't hitting your database on every request.
UPDATE - Sorry just realised you're using ASP.NET Forms and not MVC. My approach is for an MVC application so its probably not relevant although I'll leave it up here in case any MVC developers happen to stumble across your question.
I have a WCF service that returns an an object that implements both IPrinciple and IIdentity.
I assumed that somehow I could hook this up to the MVC3 authorization system without having to create a RoleProvider
e.g. so I could do something like this in my AccountController logon method:
// AthenticatedUser implments both IPrinciple and IIdentity
AthenticatedUser user = wcfService.Logon(password, userName);
FormsAuthentication.SetAuthCookie(userName, false);
// Set IPrinciple so I can use IsInRole method elsewhere (or AuthorizationAttribute can reuse it)
this.HttpContext.User = authenticationClient.AuthenticatedUser;
Then by some magic when I use
[Authorize (Roles = "foo", "bar")]
the IsInRole method of my AuthenticatedUser gets called.
However in my testing / debugging I have found that this.HttpContext.User does not seem to maintained across requests.
Edit sorry: I should have made it clear that I don't want to have to call my WCF service on each request, I'd like to somehow cache / store the user and roles and be able to use the AuthorizeAttribute with the IPrinciple comeing from my service.
Can anyone help? Thanks in advance!
If you are using IIS 7 to run this on your local machine add this to web.config under system.webServer:
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<remove name="UrlAuthorization" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<remove name="DefaultAuthentication" />
<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />
<remove name="RoleManager" />
<add name="RoleManager" type="System.Web.Security.RoleManagerModule" />
</modules>
You say "this.HttpContext.User does not seem to maintained across requests" - that is the correct behavior. Each request has a unique HttpContext.
What you might want to try is this:
FormsAuthentication.SetAuthCookie(userName, true);
, which creates a durable cookie that lasts between browser sessions.
http://msdn.microsoft.com/en-us/library/bk50ykcd.aspx
As you have found out, the HttpContext is not persisted across requests and hence neither are IPrincipal and IIdentity - the HttpContext is constructed at the start of each request by the framework, and the IPrincipal and IIdentity are constructed from the authentication ticket deserialised from the authentication cookie.
What you are describing sounds similar in some ways to a WCF authentication service. In this scenario, the service will authenticate the user and send back an authentication cookie in the response to the calling application, and that application will then use the cookie to construct the IPrincipal and IIdentity on each subsequent request.
You can override the IPrincipal and IIdentity when the Application.PostAuthenticateRequest event is raised
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if (Context.User != null)
{
var identity = Context.User.Identity;
// define our own IIdentity and IPrincipal for an authenticated user
if (identity.IsAuthenticated)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
// get the roles from somewhere
var roles = GetRoles();
identity = new CustomIdentity(ticket.Name);
IPrincipal principal = new CustomPrincipal(identity, roles);
Context.User = Thread.CurrentPrincipal = principal;
}
}
}
You can see that the roles need to be retrieved from somewhere. With a RoleProvider in place, these roles can be cached in another cookie. If you know what you're doing in terms of security, you could look at replicating how the roles are serialized and encrypted in the cookie.
An alternative may be to store the roles in session and may be adequate for a small number of roles. Bear in mind that session is not available until the PostAcquireRequestState event has been raised, some 7 events later in the application request lifecycle than PostAuthenticateRequest.
It is possible to react in your global asax on the AuthenticateRequest event and change the IIdentity like this:
public class MvcApplication : System.Web.HttpApplication
{
public override void Init()
{
base.Init();
this.AuthenticateRequest += new EventHandler(MvcApplication_AuthenticateRequest);
}
void MvcApplication_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.IsAuthenticated)
{
var name = HttpContext.Current.User.Identity.Name;
var key = "User-" + name;
var principal = HttpContext.Current.Cache["User-" + name];
if (principal == null)
{
principal = GetYourUserAsIPrincipal();
// Add to cache for 1 hour with sliding expiration
HttpContext.Current.Cache.Insert(key, principal, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(1, 0, 0));
}
HttpContext.Current.User = principal ;
}
}
}
EDIT: For caching you can use the default ASP.NET Caching. I've edited the above sample. Note, the ASP.NET cache is thread safe (http://msdn.microsoft.com/en-us/library/system.web.caching.cache.aspx)
It would be the best, if you wrap all of your cache access logic into a single class.
As with any caching mechanism: If you change your users roles, it will be reflected only when the cache has been expired.