I currently use the default forms authentication method for my ASP.NET MVC application.
above all of my actionmethods that require authentication I have this attribute
[Authorize()]
When someone tries to call the page that that action method "serves" and they haven't yet logged in, it sends them to the login page...perfect! However, if their session times out and they try to hit that page, they're also just redirected to the login page with no indication of why. I'd like to be able to determine if it's a new visit, or if it's a timeout and display a different message on the login screen accordingly.
Is that possible?
Have a look at this custom authorize attribute i have made. It was to implement some custom role based authorization, but you could make it work for you as well. There is a Session.IsNewSession property you can check to see if this request takes place on a new session.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.User.Identity.IsAuthenticated)
{
httpContext.User = new GenericPrincipal(httpContext.User.Identity, AdminUserViewModel.Current.SecurityGroups.Select(x => x.Name).ToArray());
}
return base.AuthorizeCore(httpContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("/Authentication/NotAuthorized", false);
}
else
{
if (filterContext.HttpContext.Session.IsNewSession)
{
// Do Something For A New Session
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
On sign-in, you can set a cookie that's tied to the browser session. If that cookie exists, you know that the session timed out. If not, you know it's a new visit.
Related
I'm currently working on setting up permissions for my web app. Not everyone needs to have access to certain pages, such as creating/editing/deleting (the usually) or adding new rights to users. I have a table in my database that keeps track of the users and their role/rights. I am overriding the AuthorizeAttribute. What I would like to happen is when the user is not authorized to access a page is for them to be redirected back to the page they were just at and a alert show saying they don't have access.
Example: If they are on the Home Page and click the Add New Something Button, if they don't have rights they will be directed back to the Home Page with the error.
To make this work I need to get access to the previous action/controller names since the previous page may never be the same.
Current custom AuthorizeAttribute HandleUnauthorizedRequest Method
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", filterContext.RouteData.Values["action"] },
{ "controller", filterContext.RouteData.Values["controller"] }
});
}
This gets me the action/controller they are trying to access, am I able to get where they are coming from?
Using Klings and Stephen.vakil advice I have:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
Uri requestUrl = filterContext.HttpContext.Request.UrlReferrer;
if (requestUrl != null)
{
filterContext.Result = new RedirectResult(requestUrl.ToString());
}
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "NotAuthorized" },
{ "controller", "Admin" }
});
}
}
If there was a referrer then take them back to that page, but I still need a popup to appear when they reach this page. I know that it can be done in each individual view with javascript and the alert(), but I am looking for something that can be done in one place without adjusting the other views if possible. If not, send to a generic page stating they aren't authorized.
I am new to Asp.net MVC web development and I developed one application using it. In my application I am using my own authentication and authorization check as follows:
I create Login controller in that created Login action method like this
[HttpPost]
public ActionResult Login(LoginViewModel Info)
{
if (ModelState.IsValid)
{
if (checking username and password exist in DB or not)
{
//Adding required values in session
Session["username"] = Info.Username;
//Redirect to dashboard
}
else
{
//not found redirect to login page
}
}
return View();
}
Now when accessing action methods in Admin controller I used my "custom authorize" attribute for checking user is logged-in or not and have rights for method
public class AdminController : Controller
{
[CustomAuthorize(ValidRole = "Admin")]
public ActionResult Index()
{
return View();
}
}
For this I override default AuthorizeAttribute like this
public class CustomAuthorize : AuthorizeAttribute
{
// Custom property
public string ValidRole { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["username"] == null)
{
//User is not logged-in so redirect to login page
return false;
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Login",
action = "Login"
})
);
}
}
This code works fine for me. My question that is there any better solution for checking whether user is logged-in or not and according to it redirect user to login or dashboard page so that user can't manipulate url and get access to functionality to which he is not authorized.
thanks in advance
My question that is there any better solution for checking whether
user is logged-in or not and according to it redirect user to login or
dashboard page so that user can't manipulate url and get access to
functionality to which he is not authorized.
Yes, there's already a built-in method for doing this that does not rely on ASP.NET Sessions. It is called Forms Authentication.
You don't need to be writing any custom Authorize attributes. Once you verified the credentials of the user simply set the FormsAuthentication cookie:
if (checking username and password exist in DB or not)
{
// Emitting forms authentication cookie
FormsAuthentication.SetAuthCookie(Info.Username, false);
//Redirect to dashboard
}
and then simply use the built-in Authorize attribute to decorate your protected controller actions:
public class AdminController : Controller
{
[Authorize(ValidRole = "Admin")]
public ActionResult Index()
{
// At this stage the user is authenticated and has the role Admin.
// You could get the current username using the User.Identity.Name property
return View();
}
}
Forms Authentication is stateless. It does not rely on any state on the server to track the currently authenticated user on the server. The information about the current user is contained in an encrypted forms authentication cookie that is sent along each request. This way you don't need to be thinking about handling complex scenarios when your application is hosted in a web farm in which case you would have needed to use distributed ASP.NET Sessions.
I'm using a custom authorization scheme, and when a user isn't authorized, I return an HttpUnauthorizedResult. This causes the user to be redirected to the login page. Is it somehow possible, in the login page, to detect that it is being used because of an authorization failure and tell the user this? If so, how could I do this?
It would be a bonus if I could tell the user, "You need to log in as a user with x role to perform the action you requested", or something like that.
Rather than return an HTTP 401, return a web page with the message you want, and a button to go to the login page.
Actually, you think that you are sending an Unauthorized response, but in reality ASP.NET is intercepting that HTTP 401 response and sending an HTTP 302 (Redirection) to your login page instead. So if you want a custom message, just redirect yourself to the page you want.
Cheers.
UPDATE:
If you create your own Authorize filter, you can define what happen if the user is not authorized/authenticated:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
readonly String _customError;
public MyAuthorizeAttribute(String customError)
{
_customError = customError;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
FormsAuthentication.SignOut();
filterContext.Controller.TempData["Error"] = _customError;
filterContext.Result = new RedirectResult("~/Account/yourErrorView");
}
}
(Not tested)
That way you can use your attribute this way:
[MyAuthorize("You are not authorized to see this thing")]
public ActionResult MyActionMethod()
{
return View();
}
And then the user will be redirected to "~/Account/yourErrorView", and in the TempData you will find the custom error message.
Cheers.
I think it would be better pass additional parameter which will describe the cause of error, for example:
/Account/Login?error=4
and in the Login action check if error exists.
Besides you can store your error messages in different ways: session, cookie.
Use ActionFilterAttribute instead of the AuthorizeFilterAttribute to point it to your error handling page.
public class RoleAuthorize: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = (YourController)filterContext.Controller;
try
{
if (!controller.CheckForRoleMethod())
{
throw new System.Security.SecurityException("Not Authorized!");
}
}
catch (System.Security.SecurityException secEx)
{
if (secEx != null)
{
// I use TempData for errors like these. It's just me.
TempData["ErrorMessage"] = secEx.Message;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "ErrorHandler" }, { "action", "Error" } });
}
}
}
base.OnActionExecuting(filterContext);
}
You have to make a separate method on that controller being decorated to check if the cached user is Authorized or not like so:
public class ApplicationController : Controller
{
public bool CheckForRoleMethod(){
// get formsauthentication details to retrieve credentials
// return true if user has role else false
}
}
I am implementing the security part of an ASP MVC application and I am getting confused in how to implement a custom membership provider since there seems to be a lot of functionallity that I wouldn't use.
The application I'm porting usually managed the security through an object stored in session called "SecurityManager" which contained the current user and a form permission collection. Each item of this collection contains the permission per form of the logged user and permissions per field of that form (in case they were needed if not full control of the form is assumed).
However, when I see the methods of the MembershipProvider and the AuthorizeAttribute tag they assume that I will use roles which my application does not use, we just have permission groups which are just the permissions grouped together for certain user groups, but they tend to change in time.
So basically the only thing I would need would be something that when a request is made would check if the security manager is stored in session (if it is not the user is not authenticated and will be redirected to the login page) and then get that object from session and perform operation with it to know if the user can or cannot access the view.
What would be the best approach for this? I've read that bypassing the custom membership was not a good idea.
Update: I recently came up against this and figured out how to use the AuthorizeAttribute to do exactly as you wanted. My attribute, that verifies if the user is an administrator, works as follows:
public class AuthorizeAdminAttribute : AuthorizeAttribute
{
public bool IsValidUser { get; protected set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) { throw new ArgumentNullException("httpContext"); }
// Make sure Forms authentication shows the user as authenticated
if (httpContext.User.Identity.IsAuthenticated == false) return false;
// Retrieve the unit of work from Windsor, and determine if the current user is an admin
var unitOfWork = Bootstrapper.WindsorContainer.Resolve<IUnitOfWork>();
var user = new UserByIdQuery(unitOfWork).WithUserId((int)Membership.GetUser().ProviderUserKey).Execute();
if (user == null)
return false;
// Otherwise the logged in user is a real user in the system
IsValidUser = true;
return user.IsAdmin;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext == null) { throw new ArgumentNullException("filterContext"); }
// If this user is a valid user but not an admin, redirect to the homepage
if (IsValidUser)
{
// Redirect them to the homepage
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "area", "" },
{ "action", "Index" },
{ "controller", "Home" }
});
}
else
{
// User isn't logged in, perform normal operations
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Essentially you have to user the AuthorizeCore() to determine if the user is logged in, store that result, and then perform authorization for the roles in your system. Then in your HandleUnauthorizedRequest you have to figure out if the request was unauthorized because the user wasn't logged in, or if it was because they weren't authorized.
Old Answer
I accomplish using the Authorize attribute by creating a subclass of the AuthorizeAttribute class. So for example:
public class MyCustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) { throw new ArgumentNullException("httpContext"); }
// Make sure Forms authentication shows the user as authenticated
if (httpContext.User.Identity.IsAuthenticated == false) return false;
// replace with whatever code you need to call to determine if the user is authorized
return IsUserAuthorized();
}
}
Now when a controller or action is called, and is decorated with [MyCustomAuthorize] it will run this code to determine if the user is authorized based on your custom logic, and if not will redirect them exactly how the [Authorize] attribute would.
I don't know if this is the best approach, but it is what I came up with.
In my controller, for lets say edit user. In my controller, I check if the user has rights to edit then I'd like to throw some kind of authentication or prohibited error which would lead to an error page.
Is there some way to do this rather than creating a controller and action just for error? What is the correct way to do this?
Here's an example of a custom authorize attribute you could use:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// if the user is not authenticated render the AccessDenied view
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
}
}
and then decorate your controller action with this attribute:
[CustomAuthorizeAttribute]
public ActionResult SomeAction()
{
...
}
There's one caveat with this approach you should be aware of. If the user is not authorized the server sends 200 status code which is not very SEO friendly. It would be better to send 401 status code. The problem is that if you are using Forms Authentication there's a custom module that gets appended to the ASP.NET execution pipeline and whenever the server sends 401 status code it is intercepted and automatically redirected to the login page. This functionality is by design and is not bug in ASP.NET MVC. It has always been like this.
And as a matter of fact there is a way to workaround this unpleasant situation:
You could modify the custom authorization filter like so:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// if the user is not authenticated render the AccessDenied view
filterContext.HttpContext.Items["unauthorized"] = true;
}
}
}
and in Global.asax:
protected void Application_EndRequest()
{
if (Context.Items.Contains("unauthorized"))
{
Context.Response.Clear();
Context.Response.StatusCode = 401;
Context.Server.Transfer("~/401.htm");
}
}
Now that's better. You get 401 status code with a custom error page. Nice.
Since your authorization is based per user (I suppose the correct process is each user can only edit their own data) you can't use provided Authorize filter.
Write a custom authorization filter instead. You can provide whatever functionality you'd like. The usual is to return a 401 HTTP status code.