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();
}
}
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)]
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
I have an authorization attribute on a controller, but I'd like to turn it off on one action.
I created my own authorization filter and added "Anonymous" into the Roles list. In my filter I then return true if Anonymous appears in the role list.
However, it doesn't seem to get past the login page as if the controller authorization is pre-empting anything else.
You can add [Authorize] To the controller class, and then add [AllowAnonymous] to the single action you don't want to be authorized. Example:
[Authorize]
public class AccountController : Controller
{
public ActionResult Profile()
{
return View();
}
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
}
You can create your own version of the attribute.
There is a very similar question and there is a pretty good answer how to implement your own attribute that handles this situation.
Override Authorize Attribute in ASP.NET MVC
Btw. you could also create your controller that would have authorization by default.
Base
[Authorize]
public abstract class SecureControllerBase : Controller
{
}
Usage
public class MyController : SecureControllerBase
{
}
I just did a solution using Azure ACS as the federated Identity Provider and the accepted answer didn't work for me. For those who are struggling, my solution was to bypass the security altogether for the required controller/views.
Create a new Controller/Views for those actions which you need to bypass the authorization.
And in the web.config add the following ,
<location path="TheNameOfTheControllerYouWantToBypass">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
Simply add the attribute to the Actions you want to filter, and not on the controller class. By not decorating actions, they will not be filtered, provided the controller or one of its base controllers hasn't got the attribute.
Do not add AuthorizationAttribute on your action method where ever you do not required for example.
My custom attribute
public class AuthorizationFilterAttribute : AuthorizeAttribute
{
// Some code...
}
My controller
public class UserController : BaseController, IDisposable
{
[AuthorizationFilterAttribute]
public ActionResult UserList()
{
// Authorize attribute will call when this action is executed
}
public ActionResult AddUser()
{
// Authorize attribute will not call when this action is executed
}
}
I hope you got my point what I am trying to say you.
============================ Updated Answer ================================
Create one more attribute like below.
public sealed class AnonymousAttribute : Attribute { }
Please put below code on your OnAuthorization method.
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool checkForAuthorization =
filterContext.ActionDescriptor.IsDefined(typeof(AnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AnonymousAttribute), true);
if (!skipAuthorization)
{
base.OnAuthorization(filterContext);
}
}
Let's say I put the following code somewhere in a Master page in my ASP.NET MVC site:
throw new ApplicationException("TEST");
Even with a [HandleError] attribute placed on my controller, this exception still bubbles up. How can I deal with errors like this? I would like to be able to route to an Error page and still be able to log the exception details.
What is the best way to deal with something like this?
Edit: One solution I was considering would be to add a a new controller: UnhandledErrorController. Can I put in an Application_Error method in Global.asax and then redirect to this controller (where it decides what to do with the exception)?
Note: the defaultRedirect in the customErrors web.config element does not pass along the exception info.
Enable customErrors:
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="401" redirect="~/Error/Unauthorized" />
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
and redirect to a custom error controller:
[HandleError]
public class ErrorController : BaseController
{
public ErrorController ()
{
}
public ActionResult Index ()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View ("Error");
}
public ActionResult Unauthorized ()
{
Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return View ("Error401");
}
public ActionResult NotFound ()
{
string url = GetStaticRoute (Request.QueryString["aspxerrorpath"] ?? Request.Path);
if (!string.IsNullOrEmpty (url))
{
Notify ("Due to a new web site design the page you were looking for no longer exists.", false);
return new MovedPermanentlyResult (url);
}
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View ("Error404");
}
}
As MVC is built on top of asp.net you should be able to define a global error page in web.config, just like you could in web forms eg.
<customErrors mode="On" defaultRedirect="~/ErrorHandler" />
You can create a Filter that looks for an Exception in the OnActionExecuted method:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class WatchExceptionAttribute : ActionFilterAttribute {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
if (filterContext.Exception != null) {
// do your thing here.
}
}
}
Then you can put [WatchException] on a Controller or Action Method, and it will let log exceptions. If you have a lot of Controllers, that might be tedious, so if you have a common Base Controller you can override OnActionExecuted there and do the same thing. I prefer the filter method.
As far as what page to display, you'll need to create a customErrors section in your web.config and set it up for whatever status codes you want to handle.
Example:
<customErrors defaultRedirect="GenericError.htm" mode="RemoteOnly">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
As far as logging exceptions, I would recommend using ELMAH. It integrates nicely with ASP.NET MVC sites.
Essentially I want to show a friendly message when someone is not part of a role listed in my attribute. Currently my application just spits the user back to the log in screen. I've read a few posts that talk about creating a custom attribute that just extends [AuthorizeAttribute], but I'm thinking there's got to be something out of the box to do this?
can someone please point me in the right direction of where I need to look to not have it send the user to the log in form, but rather just shoot them a "not authorized" message?
I might be a little late in adding my $0.02, but when you create your CustomAuthorizationAttribue, you can use the AuthorizationContext.Result property to dictate where the AuthorizeAttribute.HandleUnauthorizedRequest method directs the user.
Here is a very simple example that allows you to specify the URL where a user should be sent after a failed authorization:
public class Authorize2Attribute : AuthorizeAttribute
{
// Properties
public String RedirectResultUrl { get; set; }
// Constructors
public Authorize2Attribute()
: base()
{
}
// Overrides
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (String.IsNullOrEmpty(RedirectResultUrl))
base.HandleUnauthorizedRequest(filterContext);
else
filterContext.Result = new RedirectResult(RedirectResultUrl);
}
}
And if I wanted to redirect the user to /Error/Unauthorized as suggested in a previous post:
[Authorize2(Roles = "AuthorizedUsers", RedirectResultUrl = "/Error/Unauthorized")]
public ActionResult RestrictedAction()
{
// TODO: ...
}
I ran into this issue a few days ago and the solution is a bit detailed but here are the important bits. In AuthorizeAttribute the OnAuthorization method returns a HttpUnauthorizedResult when authorization fails which makes returning a custom result a bit difficult.
What I ended up doing was to create a CustomAuthorizeAttribute class and override the OnAuthorization method to throw an exception instead. I can then catch that exception with a custom error handler and display a customized error page instead of returning a 401 (Unauthorized).
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public virtual void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext)) {
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
// auth failed, redirect to login page
// filterContext.Result = new HttpUnauthorizedResult();
throw new HttpException ((int)HttpStatusCode.Unauthorized, "Unauthorized");
}
}
}
then in your web.config you can set custom handlers for specific errors:
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="401" redirect="~/Error/Unauthorized" />
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
and then implement your own ErrorController to serve up custom pages.
On IIS7 you need to look into setting Response.TrySkipIisCustomErrors = true; to enable your custom errors.
If simplicity or total control of the logic is what you want you can call this in your action method:
User.IsInRole("NameOfRole");
It returns a bool and you can do the rest of your logic depending on that result.
Another one that I've used in some cases is:
System.Web.Security.Roles.GetRolesForUser();
I think that returns a string[] but don't quote me on that.
EDIT:
An example always helps...
public ActionResult AddUser()
{
if(User.IsInRoles("SuperUser")
{
return View("AddUser");
}
else
{
return View("SorryWrongRole");
}
}
As long as your return type is "ActionResult" you could return any of the accepted return types (ViewResult, PartialViewResult, RedirectResult, JsonResult...)
Very similar to crazyarabian, but I only redirect to my string if the user is actually authenticated. This allows the attribute to redirect to the standard logon page if they are not currently logged in, but to another page if they don't have permissions to access the url.
public class EnhancedAuthorizeAttribute : AuthorizeAttribute
{
public string UnauthorizedUrl { get; set; }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var redirectUrl = UnauthorizedUrl;
if (filterContext.HttpContext.User.Identity.IsAuthenticated && !string.IsNullOrWhiteSpace(redirectUrl))
{
filterContext.Result = new RedirectResult(redirectUrl);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
The out-of-the-box behavior is that the [Authorize] attribute returns an HTTP 401. The FormsAuthenticationModule (which is loaded by default) intercepts this 401 and redirects the user to the login page. Take a look at System.Web.Security.FormsAuthenticationModule::OnLeave in Reflector to see what I mean.
If you want the AuthorizeAttribute to do something other than return HTTP 401, you'll have to override the AuthorizeAttribute::HandleUnauthorizedRequest method and perform your custom logic in there. Alternatively, just change this part of ~\Web.config:
<forms loginUrl="~/Account/LogOn" timeout="2880" />
And make it point to a different URL, like ~/AccessDenied.