I am using ASPNet Identity 2.0 (Full framework, not the core framework) and MVC.
I would like to execute C# code once the user successfully login to the site.
i know that i can write some code right after the SignInManager.PasswordSignInAsync command and it will work for new login but not will not work for users who used "remember me" feature and returned to the site later (Cookie authentication).
I am looking for an option to catch the event of all the users who signed in to the site either by entering the password and by using the "remember me" cookie.
One way you can do it is by handling the application event in your global.asax file.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
Response.Write("HELLO");
}
}
There are many ways to do it. You can create and use custom attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
readonly IAuthentication _authentication;
public CustomAuthorizeAttribute(IAuthentication authentication)
{
_authentication = authentication;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!_authentication.Authorize(filterContext.HttpContext))
filterContext.Result = new HttpUnauthorizedResult();
//your code .....
}
}
And now rather than using [Authorize] use your new [CustomAuthorizeAttribute] attribute
[CustomAuthorizeAttribute]
public ActionResult Index()
{
ViewBag.Title = "Welcome";
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
What you're after is FormsAuthentication_OnAuthenticate (I appreciate Forms-based authentication was not mentioned in the question, but it's an example of Cookie-based remember me authentication, adapt at will)
Unfortunately, there is no trigger for OnCookieBasedFormsAuthenticate_SessionCreate :)
So what you can do is check the Forms-based authentication every so often, because it (and Application_AuthenticateRequest) is fired for every request, CSS pages, images etc, going off to the database to check multiple times per request is an overly resource hungry idea. Luckily the forms cookie ticket has an issued on date and we can use that to check:
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
if ((DateTime.Now - ticket.IssueDate).TotalMinutes > 10)
{
if(/*Insert logic to check username here with ticket.Name*/)
{
//recreate cookie with new issuedate
FormsAuthentication.SetAuthCookie(ticket.Name, ticket.IsPersistent);
}
else
{
FormsAuthentication.SignOut();
}
}
Debug.WriteLine($"{ticket.Name} {ticket.IssueDate.ToUniversalTime()}");
}
catch (Exception e)
{
//Elmah
ErrorSignal.FromCurrentContext().Raise(e);
//Cannot decrypt cookie, make the user sign in again
FormsAuthentication.SignOut();
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not supported for this application.");
}
}
This is not the same as cookie expiration, because the user will still retain login status; the user should never see any login request unless there account is no longer valid.
Related
We are developing an ASP.NET MVC web application using .NET Framework 4.5 and claims-based authentication/authorization. The application uses the standard ASP.NET mechanism to save a token into a cookie to read claims between POSTs that have been previously cached into memory.
We have configured the session timeout in the web.config file like this:
<sessionState timeout="1" mode="InProc"/>
When a session timeout occurs, the Session_End event handler is called:
protected void Session_End(object sender, EventArgs e)
{
Session.RemoveAll();
FederatedAuthentication.SessionAuthenticationModule.SignOut();
}
After Session_End is executed, the Session_Start handler is triggered:
protected void Session_Start(object sender, EventArgs e)
{
Breadcrumb breadcrumb = new Breadcrumb();
Session["Breadcrumb"] = breadcrumb;
}
This behavior causes a custom filter checking whether there is session or not to return always true:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get context
HttpContext ctx = HttpContext.Current;
// If the browser session has expired...
if (ctx.Session != null && ctx.Session["Breadcrumb"] == null)
{
// Redirect to login page
}
}
}
On the other hand, if the user clicks the "Log out" menu option, the following handler is called:
public ActionResult Logout()
{
FederatedAuthentication.SessionAuthenticationModule.SignOut();
return RedirectToAction("Login", "Account");
}
After this, if we enter a valid application URL into the browser, the page is shown without being prompted for credentials. Does this mean that the token is still valid?
Could you please tell us what is happening?
Thanks very much in advance for your help.
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 am new to ASP.net MVC and created my first web application using it. In my application I am using database authentication. I have created Login action in controller which checks entered username and password exist in DB or not, If it exist then put required values in Session and redirect user to pages as per his rights else redirect user to login page. Like this
public ActionResult Login()
{
if(uservalid)
{
//set session values and redirect to dashboard
}
else
{
//redirect to login
}
}
In my application there are some functionality that can only be accessed when user is logged-in. I want to check whether user is logged-in or not before user try to access these functionality and if he is not logged-in or not have rights then redirect to login page or show some error message.
public ActionResult SomeAction()
{
//Available only when user is logged-in
}
So how do I check whether user is logged-in or not and give access to action. I read about Authorize attribute but don't know how to use it as I am using database authentication.
If you are using FormsAuthentication you don't need to use ASP.NET session to track the currently authenticated user.
I read about Authorize attribute but don't know how to use it as I am
using database authentication.
Assuming you went with FormsAuthentication, once you have validated the credentials of the user you should set a forms authentication cookie:
public ActionResult Login()
{
if(uservalid)
{
FormsAuthentication.SetAuthCookie("username", false);
return RedirectToAction("SomeProtectedAction");
}
else
{
//redirect to login
}
}
and then:
[Authorize]
public ActionResult SomeAction()
{
string currentlyLoggedInUser = User.Identity.Name;
}
By the way if you create a new ASP.NET MVC application using the internet template in Visual Studio you might take a look at the AccountController which is responsible for authenticating users and setting forms authentication cookies. Of course you could throw all the Entity Framework crap out of it and implement your own credentials validation against your own database tables.
I apply [Authorize] as well as my own customattribute for restricting the action based on permission. The code is below
[Authorize]
[FeatureAuthentication(AllowFeature=FeatureConst.ShowDashboard)]
public ActionResult Index()
{
}
Filter code
public class FeatureAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public FeatureConst AllowFeature { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
//var featureConst = (FeatureConst)filterContext.RouteData.Values["AllowFeature"];
var filterAttribute = filterContext.ActionDescriptor.GetFilterAttributes(true)
.Where(a => a.GetType() == typeof(FeatureAuthenticationAttribute));
if (filterAttribute != null)
{
foreach (FeatureAuthenticationAttribute attr in filterAttribute)
{
AllowFeature = attr.AllowFeature;
}
User currentLoggedInUser = (User)filterContext.HttpContext.Session["CurrentUser"];
bool allowed = ACLAccessHelper.IsAccessible(AllowFeature.ToString(), currentLoggedInUser);
// do your logic...
if (!allowed)
{
string unAuthorizedUrl = new UrlHelper(filterContext.RequestContext).RouteUrl(new { controller = "home", action = "UnAuthorized" });
filterContext.HttpContext.Response.Redirect(unAuthorizedUrl);
}
}
}
}
you should create a basecontroller and inherit other controlers from base controller and then check whether the session is null or not to authenticate users.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Session["User"]== null)
{
filterContext.HttpContext.Response.Redirect("/somepage");
}
}
public class SomeController : BaseController
{
}
There are multiple ways of doing it but the preferred way would be to use the Annotation. Here is a post for it
How to get custom annotation attributes for a controller action in ASP.NET MVC 4?
If you are getting started I would suggest to follow the tutorial on
http://www.asp.net/mvc
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
}
}
How to disable standard ASP.NET handling of 401 response code (redirecting to login page) for AJAX/JSON requests?
For web-pages it's okay, but for AJAX I need to get right 401 error code instead of good looking 302/200 for login page.
Update:
There are several solutions from Phil Haack, PM of ASP.NET MVC - http://haacked.com/archive/2011/10/04/prevent-forms-authentication-login-page-redirect-when-you-donrsquot-want.aspx
In classic ASP.NET you get a 401 http response code when calling a WebMethod with Ajax. I hope they'll change it in future versions of ASP.NET MVC. Right now I'm using this hack:
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 302 && Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
Context.Response.Clear();
Context.Response.StatusCode = 401;
}
}
The ASP.NET runtime is developed so that it always will redirect the user if the HttpResponse.StatusCode is set to 401, but only if the <authentication /> section of the Web.config is found.
Removing the authentication section will require you to implement the redirection to the login page in your attribute, but this shouldn't be a big deal.
I wanted both Forms authentication and to return a 401 for Ajax requests that were not authenticated.
In the end, I created a custom AuthorizeAttribute and decorated the controller methods. (This is on .Net 4.5)
//web.config
<authentication mode="Forms">
</authentication>
//controller
[Authorize(Roles = "Administrator,User"), Response302to401]
[AcceptVerbs("Get")]
public async Task<JsonResult> GetDocuments()
{
string requestUri = User.Identity.Name.ToLower() + "/document";
RequestKeyHttpClient<IEnumerable<DocumentModel>, string> client =
new RequestKeyHttpClient<IEnumerable<DocumentModel>, string>(requestUri);
var documents = await client.GetManyAsync<IEnumerable<DocumentModel>>();
return Json(documents, JsonRequestBehavior.AllowGet);
}
//authorizeAttribute
public class Response302to401 : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
Data = new { Message = "Your session has died a terrible and gruesome death" },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.StatusDescription = "Humans and robots must authenticate";
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
}
//base.HandleUnauthorizedRequest(filterContext);
}
}
You could also use the Global.asax to interrupt this process with something like this:
protected void Application_PreSendRequestHeaders(object sender, EventArgs e) {
if (Response.StatusCode == 401) {
Response.Clear();
Response.Redirect(Response.ApplyAppPathModifier("~/Login.aspx"));
return;
}
}
I don't see what we have to modify the authentication mode or the authentication tag like the current answer says.
Following the idea of #TimothyLeeRussell (thanks by the way), I created a customized Authorize attribute (the problem with the one of #TimothyLeeRussell is that an exception is throw because he tries to change the filterContext.Result an that generates a HttpException, and removing that part, besides the filterContext.HttpContext.Response.StatusCode = 401, the response code was always 200 OK). So I finally resolved the problem by ending the response after the changes.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class BetterAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
//Set the response status code to 500
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.HttpContext.Response.StatusDescription = "Humans and robots must authenticate";
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
filterContext.HttpContext.Response.End();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
You can call this method inside your action,
HttpContext.Response.End();
Example
public async Task<JsonResult> Return401()
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
HttpContext.Response.End();
return Json("Unauthorized", JsonRequestBehavior.AllowGet);
}
From MSDN: The End method causes the Web server to stop processing the script and return the current result. The remaining contents of the file are not processed.
You could choose to create a custom FilterAttribute implementing the IAuthorizationFilter interface.
In this attribute you add logic to determine if the request are supposed to return JSON. If so, you can return an empty JSON result (or do whatever you like) given the user isn't signed in. For other responses you would just redirect the user as always.
Even better, you could just override the OnAuthorization of the AuthorizeAttribute class so you don't have to reinvent the wheel. Add the logic I mentioned above and intercept if the filterContext.Cancel is true (the filterContext.Result will be set to an instance of the HttpUnauthorizedResult class.
Read more about "Filters in ASP.NET MVC CodePlex Preview 4" on Phil Haacks blog. It also applies to the latest preview.