ASP.NET MVC Forms authentication and unauthenticated controller actions - asp.net-mvc

I have a ASP.NET MVC site that is locked down using Forms Authentication. The web.config has
<authentication mode="Forms">
<forms defaultUrl="~/Account/LogOn" loginUrl="~/Account/LogOn" timeout="2880"/>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
None of my pages other than Account/LogOn can be viewed unless the user is authenticated.
Now I am trying to add PayPal IPN to my site and in order to do that I need to have two pages that handle PayPal's payment confirmation and thank you page. These two pages need to be available for anonymous users.
I would like these pages to be controller actions off my Account controller. Is there any way I can apply an attribute to specific action methods that make them available to anonymous users? I found a several posts here that attempt to do that but there was most people wanted the opposite scenario.
Basically I want may AccountController class to have no authorization for most of the methods except for a few. Right now it looks like only the LogOn method is available to anonymous users.

Yes you can. In your AccountController there's an [Authorize]-attribute either on class-level (to make the whole controller restricted) or on specific methods.
To make specific actions restricted you simply use the Authorize-attribute on the methods that handle these actions, and leave the controller-class unrestricted.
Here are a few examples... hope it helps
To require users to login, use:
[Authorize]
public class SomeController : Controller
// Or
[Authorize]
public ActionResult SomeAction()
To restrict access for specific roles, use:
[Authorize(Roles = "Admin, User")]
public class SomeController : Controller
// Or
[Authorize(Roles = "Admin, User")]
public ActionResult SomeAction()
And to restrict access for specific users, use:
[Authorize(Users = "Charles, Linus")]
public class SomeController : Controller
// Or
[Authorize(Users = "Charles, Linus")]
public ActionResult SomeAction()
As you can see, you can either use the attribute at class-level or at method-level. Your choice!

I don't think there is an "Unauthorize" attribute that can be applied to actions and if you don't want to place "[Authorize]" on all but two actions in a controller try the following:
Here are two methods I can think of:
1- Location attribute in Web.config (Not sure if this will work with MVC routing etc.)
After your
<system.web> stuff </system.web>
in web.config file, add the following:
<location path="Account/ActionOne">
<system.web>
<authorization>
<allow users ="*" />
</authorization>
</system.web>
</location>
Where Account/ActionOne is the name of the action method you want to give anonymous access to. For the second Action, copy the above code and paste it right after it and change the name of the Action.
I'm not sure if this will work because of MVC routing etc, but give it a try.
2- Base Controller
If the previous solution didn't work, your best bet would be to create a base controller that is decorated with the Authorize attribute:
[Authorize]
public class AuthorizeControllerBase : Controller {}
Then have all your controllers inherit from it:
public class AccountController : AuthorizeControllerBase
{
// your actions etc.
}
This will make any controller that inherits from AuthorizeControllerBase require authorization/logging in to invoke any methods.
Then you would need to remove from your web.config

Instead of securing all resources on your website by default and then looking for a way to provide anonymous access for individual resources, you're probably better off taking the opposite approach. Don't specify authorization rules in your web.config, then use Authorization filters (see Mickel's answer) to secure individual controllers and/or actions.

Related

Why is AllowAnonymous not working while deployed to Azure Websites?

I have a MVC4 web app with the following controller
[Authorize]
public class AccountController : BaseController
{
[AllowAnonymous]
public ActionResult SignInRegister(LoginModel loginModel, string returnUrl)
{
//some implementation
}
//other secured actions
}
This is working as expected when running locally, but as soon as I deploy it to the Free Azure Website I get a 401 error code with the message: You do not have permission to view this directory or page.
Removing the [Authorize] attribute and redeploying works as expected, adding it again and redeploying brings back the problem.
I even tried the fully qualified class names: System.Web.Mvc.Authorize and System.Web.Mvc.AllowAnonymous with the same results.
The app is using .NET 4.5 and the Azure Website is also configured to use 4.5.
UPDATE:
The BaseController has an action that returns the Header as partial view which was not decorated with [AllowAnonymous]. Locally it resulted in the page being displayed without the header, but on Azure Websites the response was cut off and only returned with the error message mentioned above. I had not realized the header was missing until I purposely looked into it.
Now the question begs to be asked: why is Azure Websites overriding the response?
The BaseController has an action that returns the Header as partial view which was not decorated with [AllowAnonymous]. Locally it resulted in the page being displayed without the header, but on Azure Websites the response was cut off and only returned with the error message mentioned above. I had not realized the header was missing until I purposely looked into it.
Now the question begs to be asked: why is Azure Websites overriding the response?
I had the exact same problem and like Jonas' update says, you need to look out for Actions that return Partial Views AND have the [Authorize] attribute.
What you need to do is to remove the [Authorize] attribute and then if your action needs the user to be authenticated to render properly, have your code handle the unauthorized case.
Example is if your page displays the currently logged in user's name via a Partial. Have your action display an empty string or something else if the currently logged in user is not available.
Check your web.config if you have
<authorization>
<deny users="?" />
</authorization>
its override [AllowAnonymous]
Add to web.config section:
<location path="YourController/AnonymousMethod">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
to allow anonymous access for AnonymousMethod

Asp.Net MVC 4 User is authenticated even if not authorized

I am working in my first MVC project and i am having troubles with authentication.
I have a login page that correctly validates the user by my active directory. But, even authenticated not all users are authorized to access the system, so I use a section in web.config to check if user have permissions. Something like:
<authorization>
<allow users="john,mary,paul,bill,jane,anna" />
<deny users="*" />
</authorization>
It works fine and the user always is redirected to login if doesn't have permission.
BUT, when I check if user is Authenticated, the result is always true. And, in login page, I want to check if I must show a message to logged AND authorized users. Something like:
#if (User.Identity.IsAuthenticated && User.Identity.IsAuthorized)
{
#Html.Partial("_Menu");
}
So... How I do it?
Authentication an Authorization are 2 different concepts. Authentication means you know who the person is. Authorization means they have specific permissions.
If you want to check if they are authorized to perform some action and provide them a button or link to perform that action (or access some data, whatever it may be), then you'll have to check if they have the permissions using other means. Some more specifics about the setup you have would help answer the question better.
To Authenticate, check on your web.config if you have this tag:
<authentication mode="Forms">//It cannot be "None"
<forms loginUrl="~/MyLoginURL" timeout="2880" />
</authentication>
And in your LoginMethod, that authenticate the user, make sure you called any .NET method that authenticate the user on its Identity, example:
using System.Web.Security;
FormsAuthentication.SetAuthCookie(login, boolean to cookie) // Or others FormsAuthentication methods that authenticate.
There is an AuthorizeAttribute in System.Web.Mvc, that can be used with your actions or controllers, like:
[Authorize]
public ActionResult Index()
{ ... }
or
[Authorize]
public class HomeController
{ ... }
This AuthorizeAttribute will check if the current user is Authorized. You can create your own attribute by just inheriting the AuthorizeAttribute and override OnAuthorization and AuthorizeCore
Once you perform a successful login (FormsAuthentication.SetAuthCookie), your user get authenticated so until you dont explicitly logoff (FormsAuthentication.SignOut) that's the correct behaviour, as said authentication and authorization are two different things.
I guess you want to show a menu only to users that have been authenticated through AD, in this case you can just restrict your resources with:
#if (User.Identity.IsAuthenticated)
{
#Html.Partial("_Menu");
}
If authorized, a user is also authenticated so no need to do the double check. You can then use the [Authorize] attribute, which relies on User.Identity.IsAuthorized.
Authorization comes handy when you can use Roles to group your users. Check if your ADusers belong to any group, if so you could do something like:
<authorization>
<allow users="*" allow role="yourdomain\yourgroup" />
<deny users="*" />
</authorization>

ASP.NET MVC 3 AuthorizeAttribute not working, not even being called

I was having trouble getting AuthorizeAttribute to work for an out of the box MVC 3 app. So I created my own attribute inheriting from AuthorizeAttribute and overriding all methods and adding break points to see what was going on.
But the problem is, the attribute code is NEVER called! Any idea what could cause this?
It is a completely blank MVC 3 app with a HomeController with an Index method. Form authentication set to redirect to ~/Account/LogOn. But it seems it just doesn't load the [Authorize] attribute...
EDIT:
Sorry guys, I must really be tired today :) it is in fact not a totally blank project. I have some Ninject code that provides a repository to my HomeController. If I disable this and create a parameterless constructor on the HomeController the AuthorizeAttribute seems to work ok.
Any idea why Ninject dependency injection would interfere with the Authorize attribute?
--
Christian
And you say you have the following settings on your web.config?
<authentication mode="Forms">
<forms loginUrl="~/Account/logOn" />
</authentication>
How are you using your authorize attribute, could we see some code?
You might want to take a look at this step by step "Authenticating Users with Forms Authentication" guide.
Creating a custom attribute and enabling Forms auth aren't enough. You still have to decorate the Controllers / Actions for which you want the filter to execute with the attribute.
[Authorize]
public ActionResult Index()
{
// ...your code
}
or
[Authorize]
public class HomeController
{
// ... your actions
}
Edit As tpeczek said in comments, also ensure that [Authorize] is using your AuthorizeAttribute and not System.Web.Mvc.AuthorizeAttribute.

Area level security for asp.net mvc

I know it is possible to decorate a controller with the Authorize attribute to control access, what I don't know is the accepted or proper way to enforce security across all the controllers/views in an Area.
Is there something in web.config, area registration or some other place to apply authorization security?
A convenient way is to create a new base class
[Authorize]
public abstract class AuthorizeBaseController : Controller
{
}
and make sure that all of your controllers for which you require authorization (in your case, everything in the area that you're concerned about) descend from AuthorizeBaseController.
public class HomeController : AuthorizeBaseController
{
public ActionResult Index()
{
return View();
}
}
The [Authorize] attribute should affect all of the descendents of the new base class.
Edit The issue that I have with using the <location path="" > approach is that, since the routing engine makes it's possible for any route to call any controller, setting authorization based on the url (and thus a specific route) instead of the controller actions makes it possible to call a controller that should be protected and skip the authorization. That wasn't an issue in webforms since a page was a page (and not a method call), but the separation between page/path and code in MVC makes this a huge security hole.
The only safe way of doing this in an MVC application is to do what David suggests - attributing a base controller and having all controllers in the area subclass that base controller.
Using a <location> tag for authorization in MVC will open security holes in your application. You're not interested in securing URLs or routes. You want to secure the controllers themselves, since they're the actual resources you're trying to protect. Therefore the protections need to be placed directly on the controllers.
Furthermore, remember that an area is really just a fancy way of grouping routes, not controllers. Trying to use fancy logic to detect the current area and infer authorization settings will also open security holes in your application.
As was already suggested, you can make use of the <location /> element in your web.config. Otherwise, you can use a base controller class per-area and decorate that with the AuthorizeAttribute so that all controllers which inherit from it are also filtered.
you can always use
<location path="" >
<system.web>
<authorization>
deny or allow
</authorization>
</system.web>
</location>

Problem with Ajax Call to Action Method on Controller Requiring Authenticated User

Using a technique I found in one of the recent ASP.NET MVC books, I have an action method on a controller that returns a partial view for an ajax request and a full view actionresult for a normal get request -- it checks the IsAjaxRequest property of the Request object to determine which type of actionresult to return. The partial view returned by the action method returns the HTML to display a table of records from a database. The controller that contains the action method is tagged with the Authorize attribute so that only logged in users can call the controller's methods. I am using forms authentication with a 30-minute timeout and sliding expiration.
The problem arises after the user's 30-minute timeout has been reached. Because the controller is marked with the Authorize attribute, a call to the action method after the expiration timeout redirects the user to the login page. However, because this is an ajax call, the html for my login page is returned and rendered in the middle of the page that should contain the HTML table of records that would normally be returned by the action method in the partial view. The ajax call is not really failing, just returning the html for the wrong page.
Has anyone encountered and dealt with this problem? I am trying to avoid having to move all of my server side code that handles ajax calls into a separate controller that does not require an authenticated user, but that seems like my only alternative at this point. Even that won't produce the behavior I would expect, because it will allow the user to continue using the web page even after the 30 minute timeout has been reached -- it will not redirect to the login page.
Thanks for any advice.
Edit
The solution below with the custom AuthorizeAttribute would seem to have me heading in the right direction, but I cannot even get to this code. It appears that the code in the custom AuthorizeAttribute is never reached after the expiration timeout is reached. It seems like forms authentication is causing a redirect to the login page long before the attribute code. The custom AuthorizeAttribute is the only one on my controller. I also have the following web.config values (the timeout value is set extremely low to trigger a timeout for testing):
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="1" slidingExpiration="true" defaultUrl="~/ErrorReport/Index" requireSSL="true" protection="All"/>
</authentication>
<authorization>
<deny users="?"/>
<allow users="*"/>
</authorization>
<location path="Content">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<location path="Scripts">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
Are the authorization web.config elements getting in the way? Should I not be using them with ASP.NET MVC?
I actually have a blog post queued up about this that I'll add to the comment when it has been posted, but in the time being here's what's happening.
At a high level, when the ASP.NET runtime sees a 401 response code it will automatically convert it to a redirect and send the user to the login page. What you want to do is bypass that 401.
In MVC, when you're using the AuthorizeAttribute, it checks to see if the user is authorized and, if not, returns an HttpUnauthorizedResult. This basically forces the runtime to redirect the user to the login page. What you want to do is override this behavior.
To do this, extend the AuthorizeAttribute as such:
public class AuthorizeWithAjaxAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult && filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 200;
filterContext.Result = /* Some result recognized by the client */
}
}
}
When you make an AJAX request and the data of the response is equal to what you return as the filter context's result, simply redirect the user to the login page via javascript.
Yes, this problem is fairly common. It also has a simple solution - in the part of the Javascript which is handling the AJAX response, check for a timeout before using the response. If the timeout has occurred, redirect the user to the login page.
Alternately, if the server-side script is itself checking for a session timeout (which appears to be your case), it becomes even simpler. Do not return an entire login page, instead just return a flag like 'need_login'. Further, simply write the Javascript handler to check if the returned value is 'need_login' and load the login page if true.

Resources