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.
Related
I have mini-profiler working, but now I am trying to restrict access by setting functions as described under "Profiler Security" at http://miniprofiler.com/
MiniProfiler.Settings.Results_Authorize = IsUserAllowedToSeeMiniProfilerUI;
MiniProfiler.Settings.Results_List_Authorize = IsUserAllowedToSeeMiniProfilerUI;
My IsUserAllowedToSeeMiniProfilerUI function needs to look at the results of the ClaimsPrincipal, which is modified by a custom globally registered Authorization filter.
When I watch the calls, the primary request is authorized as expected, and IsUserAllowedToSeeMiniProfilerUI returns true. However, the http request that retrieves the profiler results (~/mini-profiler-resources/results or ~/mini-profiler-resources/results-index) bypasses my global authorization filter, so the ClaimsPrincipal isn't correctly modified for that request, and IsUserAllowedToSeeMiniProfilerUI incorrectly returns false due to that.
I register mini-profiler's filter as GlobalFilters.Filters.Add(new ProfilingActionFilter()), and also have the handler registered in the web.config as
<system.webServer>
<handlers>
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*"
type="System.Web.Routing.UrlRoutingModule"
resourceType="Unspecified" preCondition="integratedMode" />
<!-- ... -->
</handlers>
My custom authorization filter is registered in the Global.asax by adding it as GlobalFilters.Filters.Add(new MyAuthorizationFilter())
Why is mini-profiler bypassing my authorization filter?
GlobalFilters is centric to the MVC framework. So by default the globally registered authorization filters will only take effect for requests handled by the mvc framework - not for all http requests.
MiniProfiler implements its client-side results in a single class MiniProfilerHandler that implements both IRouteHandler and IHttpHandler. (The default profiler provider WebRequestProfilerProvider ensures that the routes for MiniProfilerHandler are registered. The web.config registration of UrlRoutingModule ensures that the mini profiler urls are actually routed.)
Thus mini profiler handles the profiler results http requests directly as an http handler that is located by routing - outside of the normal MVC request flow.
So it is necessary to handle any custom authorization logic in the e.g. IsUserAllowedToSeeMiniProfilerUI method taking into account that any authorization filters (or other action filters for that matter) are not guaranteed to have run. Note however, that it appears that Results_Authorize is also invoked at some stage once profiling is started, so you also must handle the case where your filters have run.
See also: Understanding the MVC Application Execution Process
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
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>
in my MVC 3 application on login it executes the following:
System.Web.Security.FormsAuthentication.SetAuthCookie(userName, false);
My web.config has the following:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
After that I set a few session variables. Problem is that if I leave a page for long enough for the session to expire, then click on a page then instead of redirecting me to the login screen, it is raising an exception in the controller with the null reference of the session variable.
public ActionResult Fixit(int Id, int cardId)
{
var model = new FixitVM();
model.PointsMaxValue = (int)Session["MAXPoints"];
}
Is this expected behavior? I would have thought the redirect to the login page would happen before this code fired.
Is there some configuration I can set to make the redirect happen before it tries to evaluate this code?
Authentication and session are managed by two different cookies. It's possible that the session cookie has expired while the authentication cookie is still good.
I've implemented something like this:
Windows Authentication with ASP.NET MVC
Background
- this is an intranet application and uses Active Directory
- the application maintains its own list of users (i.e. network logins) and roles
- I have a custom Membership and RoleProvider classes that performs the Membership.ValidateUser logic, etc.
- the validation logic:
* if the user (network login) is in the application user table
they are Signed-in
* upon access to any URL/action, auto-sign in if not already
* if user NOT in system, redirect them to a "NoPermission" page
* the [Authorize(Roles = "blah")] attribute is also used
Here's my controller OnAuthorize code on my controller:
protected override void OnAuthorization(AuthorizationContext authContext)
{
if (!this.HttpContext.Current.User.Identity.IsAuthenticated)
{
string networkLogin = HttpContext.Current.Request.LogonUserIdentity.Name;
if (this.MembershipService.ValidateUser(networkLogin))
{
FormsAuthentication.SignIn(networkLogin, true);
base.OnAuthorization(authContext);
}
}
}
And here's a snippet from my Web.Config
<authentication mode="Forms">
<forms loginUrl="~/Home/NoPermissionIndex"
timeout="1"
defaultUrl="~/Home/Index"
/>
</authentication>
The Problem
Most things work EXCEPT on first-time access, the user gets logged in but is still redirected to the "NoPermission" page. Further, when the session/cookie/ticket (what's the difference, btw?), the user is also redirected to the "NoPermission" page.
I read from here
ASP.NET MVC HttpContext Getting IdentityName after LogOn
that a likely problem might be due to the authentication cookie not being present in the request, and that we have to send it in the response so that the client browser will send it in the request on subsequent calls.
If that's the case, how do I solve this on first access?
And secondly, how do I make the cookie never expire? (Or just give timeout a really big number? Or some other solution?)
Help please!
Thanks.