In my controller the [Authorized] annotation.
I'd like to go get a list of authorized users that are setup in my web.config file.
<add key="authorizedUsers" value="jeff,dan,mindy,claudia"/>
I know in the controller you can do something like:
[Authorize Users="jeff,dan,mindy,claudia"]
But I'd rather just update the web.config file without having to re-compile. Is there anyway to do read the web.config file for my list and then add it to the [Authorize] attribute? I'm also using Windows Authenticationfor this rather than Form Authentication.
You can implement custom AuthorizeAttribute which inherits from AuthorizeAttribute.
I assume you are using FormAuthentication. Otherwise, it won't work.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomUserAuthorizeAttribute : AuthorizeAttribute
{
private string[] _usersSplit
{
get
{
var authorizedUsers = ConfigurationManager.AppSettings["authorizedUsers"];
return authorizedUsers.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
IPrincipal user = httpContext.User;
return user.Identity.IsAuthenticated && (_usersSplit.Length <= 0 || Enumerable.Contains(_usersSplit, user.Identity.Name, StringComparer.OrdinalIgnoreCase));
}
}
Usage
[CustomUserAuthorize]
public ActionResult Test()
{
ViewBag.Message = "Your page.";
return View();
}
FYI: Ideally, you want to use role based authentication, and store them in database. It is a little bit easy to maintain. However, it is up to your need.
You don't do it like that. You need to setup these users into roles and then grant the roles access in the web.config file.
<system.web>
<authorization>
<allow roles="admin"/>
</authorization>
</system.web>
You can do this;
<authorization>
<allow users="?"/>
<deny users="*"/>
</authorization>
but that opens the website to potential hacking IMO
Related
I have a ASP.NET website set up with Windows Authentication for a specific domain group (MYDOMAIN\MY_SITE_USERS). I want to add a controller with some actions that can be performed from a special Windows account, without access to the rest of the website.
So:
~ ==> only MYDOMAIN\MY_SITE_USERS
~/DoSomething ==> only MYDOMAIN\MY_SITE_USERS
~/SpecialAction/Do ==> only MYDOMAIN\SPECIAL_ACCOUNT
I've seen other answers (using location in Web.Config) for example:
<location path="~/SpecialAction/Do">
<system.webServer>
<security>
<authorization>
<add accessType="Deny" users="*"/>
<add accessType="Allow" users="MYDOMAIN\SPECIAL_ACCOUNT"/>
</authorization>
</security>
</system.webServer>
</location>
but my the problem is that with the above, then SPECIAL_ACCOUNT can access all the other pages since I need to add to the general:
<authentication mode="Windows" />
<identity impersonate="true"/>
<authorization>
<allow users="MYDOMAIN\SPECIAL_ACCOUNT" />
<allow users="MYDOMAIN\MY_SITE_USERS"/>
<deny users="?" />
<deny users="*" />
</authorization>
otherwise MYDOMAIN\SPECIAL_ACCOUNT can't login at all.
Have you tried to use any approach similar to the following one?
public static class ApplicationRoles
{
public const string SpecialAccount = #"domain\Special Account";
public const string MySiteUsers = #"domain\My Site Users";
}
[Authorize(Roles = ApplicationRoles.SpecialAccount)]
public class SpecialAction()
{
//stuff
}
[Authorize(Roles = ApplicationRoles.MySiteUsers)]
public class DoSomething()
{
//stuff
}
If you are looking for a web.config based solution, it would be worthy to have a look at Dynamic Controller/Action Authorization in ASP.NET MVC.
Use an action filter on the controllers that require protection.
public class FilterAccountsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string username = filterContext.HttpContext.User.Identity.Name;
//do your logic here for access.
//if allow no need to do anything
//else redirect to error page etc?
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "Error" },
{ "controller", "Home" },
{"area", ""}
});
}
}
Then use like so:
[FilterAccounts]
public class HomeController : Controller
{
}
You could even extend the above to take arguments. If you can shove all your logic into one filter then you only need to remember to add it to all your controllers with the argument needed for it's protection.
[FilterAccounts(FilterEnum.OnlySpecialAccount)]
[FilterAccounts(FilterEnum.OnlySiteUsers)]
I've been racking my brain over this for the past week, and none of the answers I've found here or elsewhere seem to be doing anything. I have an ASP.NET MVC5 application that uses SimpleMembership. I have a controller called OrganisationsController that has the following attribute:
[Authorize(Roles = "Administrator")]
I've checked the database and the user I'm logging in with is indeed in the "Administrator" role. However, neither the Authorize attribute nor User.IsInRole() return "true" for this role.
In Authorize attribute not working with roles it is suggested that
The AuthorizeAttribute calls the IsInRole method on the IPrincipal instance stored in HttpContext.User. By default IPrincipal has no roles, and in this case IsInRole will always return false. This is why access to your action is denied.
I've used the following code as suggested in that answer, but authTicket.UserData remains empty.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] roles = authTicket.UserData.Split(',');
GenericPrincipal userPrincipal = new GenericPrincipal(new GenericIdentity(authTicket.Name), roles);
Context.User = userPrincipal;
}
}
I can't figure out what's going wrong. Why can I log in, but can't any of the roles be found?
Here's some relevant parts of the web.config:
<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
<providers>
<add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
</providers>
</roleManager>
<membership defaultProvider="SimpleMembershipProvider">
<providers>
<add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" cookieless="UseCookies" />
</authentication>
and this is the InitializeSimpleMembershipAttribute I've defined:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
if (!WebSecurity.Initialized)
{
WebSecurity.InitializeDatabaseConnection("VerhaalLokaalDbContext", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
The InitializeSimpleMembershipAttribute is only set on the AccountController.
What's exactly is going on here? Why can't the roles, which are defined and tied to users in the database, be found?
We used the System.Web.Security.Roles.GetRolesForUser(...) call and then brute force check those role arrays for an intersection. In our case this all happens inside a custom AuthorizeAttribute classes' () call. An extended attribute may not necessary for you but I wanted to give the following code snippet some context. We only used the principal to get the user name.
e.g.,
var userRoles = System.Web.Security.Roles.GetRolesForUser(username);
var allowedRoles = Roles.Split(','); // Roles is a property on the Authorize attribute
var matches = userRoles.Intersect(allowedRoles).ToArray();
if ( matches.Length > 0 ) // true if user is in an allowed role, otherwise it is not
Hope that helps! I'm sure there is a more efficient way, I just dusted off what we have been using for two years now.
i am new in MVC but worked with web form for a long time. i used to protect pages in webform using location tag. so when any one try to access my product.aspx or SalesReport.aspx then user redirect to login page and after login they can access those pages.
<location path="product.aspx">
<system.web>
<authorization>
<deny users="*" />
</authorization>
</system.web>
</location>
<location path="SalesReport.aspx">
<system.web>
<authorization>
<deny users="*" />
</authorization>
</system.web>
</location>
so in MVC there is no concept like pages rather here we use controller and action method.
so guide me what people does in mvc to protect private pages through coding and config files
can we use location tag in mvc if yes then how......give me some sample.
how could i protect full controller and some time want to protect few action method inside the controller.
i guess protection can be done by coding and as well as manipulation config file. so i am looking for two different kind of approach.
so guide me how to protect files through code and how to protect files through manupulating config file like using location tag. thanks
You can write a custom action filter and apply that to your controllers /action methods as needed. This custom filter will check whether user is logged in or not and then either redirect the user to the login page or continue execution ( execute the action method and return a response )
Some thing like this. You may keep this in a BaseController class and inherit your other controllers from that.
public class BaseController : Controller
{
public class VerifyLogin : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool validUser;
// check user is logged in or not here
//you may check Identity or Session or whatever method you want
//and set validUser value to true if user is valid/logged in
if(validUser)
{
return;
}
string baseUrl = filterContext.HttpContext.Request.Url.Scheme
+ "://" + filterContext.HttpContext.Request.Url.Authority
+ filterContext.HttpContext.Request.ApplicationPath.TrimEnd('/')
+ "/";
//Redirect user to login page
filterContext.Result = new RedirectResult(baseUrl + "account/login");
}
}
}
You can apply this filter on a controller or action methods.
Controller level
[VerifyLogin]
public ProductController : BaseController
{
public ActionResult Index()
{
return View();
}
}
Action method level
public SalesController : BaseController
{
[VerifyLogin]
public ActionResult SecretReport()
{
return View();
}
public ActionResult PublicReport()
{
//filter is not applied to this action method
return View();
}
}
Title says it all, i want it so if the user tries to navigate to any page or access anything in the website to be redirected to a login page, that's only if hes not logged in.
This is my simple login code :
public ActionResult login(MvcMobile.Models.Users x)
{
var user = db.Users.FirstOrDefault(u => u.username == x.username && u.password == x.password);
if (user == null)
ModelState.AddModelError("","username or password is wrong.");
else
{
FormsAuthentication.SetAuthCookie(user.username, false);
Response.Redirect("/");
}
return View(user);
}
i tried to place this code in the main layout, but i got an infinite redirect loop :P
#if (User.Identity.IsAuthenticated)
{
<p> Hi #User.Identity.Name</p>
}
else
{
Response.Redirect("~/home/login");
}
any idea ?
Decorate your controllers/actions with the [Authorize] attribute instead. Or if you want this to apply to all controllers add it as a global action filter:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizeAttribute());
}
Of course by doing this you might want to exclude your AccountController from authentication by decorating it with the [AllowAnonymous] attribute:
Then in your web.config, set the loginUrl page of your forms authentication tag to point to the login page where all anonymous users will be redirected to:
<authentication mode="Forms">
<forms loginUrl="~/home/login" />
</authentication>
In addition to Darin Dimitrov's answer, you should also config the LoginUrl attribute at the Web.config file, for example:
<authentication mode="Forms">
<forms loginUrl="member_login.aspx"
defaultUrl="index.aspx" />
</authentication>
What's the data source Asp.net MVC uses to see if the user is in which role. And how can i change it so that it works with my own database table (when i write [Autorize(Roles="admin")] it checks in the table if the user is in the role )
What's the data source Asp.net MVC uses to see if the user is in which role.
It uses the RoleProvider that is configured in your web.config. If you want to use custom tables you could write a custom role provider by inheriting from the RoleProvider class and implementing the abstract members. The IsUserInRole method is the one that you should always implement because that's what will be used in this case:
public class MyRoleProvider: RoleProvider
{
public override bool IsUserInRole(string username, string roleName)
{
// go and hit your custom datasource to verify if the user
// is in the required role and return true or false from this
// method
...
}
}
Then you could register your custom role provider in web.config in order to replace the default one:
<system.web>
...
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<add name="MyRoleProvider" type="Mynamespace.MyRoleProvider" />
</providers>
</roleManager>
</system.web>
And if you don't want to be using any providers (judging from your previous question that seems to be the case) then you should write a custom Authorize attribute which is not using a role provider at all but is using some custom code of yours:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.User.Identity.IsAuthenticated)
{
// no user is authenticated => no need to go any further
return false;
}
// at this stage we have an authenticated user
string username = httpContext.User.Identity.Name;
return IsInRole(username, this.Roles);
}
private bool static IsInRole(string username, string roles)
{
// the username parameter will contain the currently authenticated user
// the roles parameter will contain the string specified in the attribute
// (for example "admin")
// so here go and hit your custom tables and verify if the user is
// in the required role
...
}
}
and finally decorate your controller action with this custom attribute instead of relying on the default one which is based on the role provider:
[MyAutorize(Roles = "admin")]
public ActionResult Index()
{
...
}