This is a two-parter
Question 1 (the real question)
I have a DashboardController that is not tied to a model. A User must be logged in before they can access the dashboard. How can I run a check to see if a user is authenticated before every action is executed, and redirect them to the login view if not? I think OnActionExecuted is what I want, but I am not sure what the exact implementation should be. Am I on the right track here?
public class DashboardController : Controller
{
private ApplicationContext db = new ApplicationContext();
//
// GET: /Admin/
public ActionResult Index()
{
var categories = db.Categories.ToList();
return View(categories);
}
public ActionResult Product(int id)
{
var product = db.Products.Find(id);
return View(product);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(Session["current_user"] == null)
{
// This (obviously) doesn't work - what should go here?
return RedirectToAction("Create", "Session");
}
base.OnActionExecuted(filterContext);
}
}
Question 2
If the user IS logged in, what is the right way to make the user accessible in all of these views? I have been told ViewBag is generally a bad idea - what should I use?
I can authorize controllers and actions by follow this link:
It's in brazilian portuguese originally, but the link below is translated to english.
https://translate.google.com.br/translate?sl=pt&tl=en&js=y&prev=_t&hl=pt-BR&ie=UTF-8&u=http%3A%2F%2Fdevbrasil.net%2Fprofiles%2Fblogs%2Fautentica-o-e-permiss-es-de-usu-rios-em-asp-net-mvc-4&edit-text=&act=url
You can get the logged user in views by
#HttpContext.Current.User.Identity.Name
PS: Sorry my bad english
Use [Authorize] atrribute.
For example:
[AcceptVerbs(HttpVerbs.Get)]
[Authorize]
public ActionResult add()
{
}
Then in the web.config
<authentication mode="Forms">
<forms name="my_cookie_name" loginUrl="~/login" defaultUrl="~/" timeout="2880"/>
</authentication>
If the user is not authenticated, it will get redirected automatically to the login page.
If you want something simple to control the identity of your users check the highest rated answer here: ASP.NET MVC - Set custom IIdentity or IPrincipal. It's a brilliant example. I use somthing similar in all my projects.
In my login action:
var user = _userService.system_login(systemlogin_model_post.email, systemlogin_model_post.password); // my user model
//... doing all sorts of validations
// once everyone is happy I create a cookie
Response.Cookies.Add(UserCookie.GetCookie(user));
Than using the code from the link above I create cookie:
public static class UserCookie
{
public static HttpCookie GetCookie(User user)
{
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel { user_id = user.UserId, username = user.Username, roles = user.Roles ,session_token = GUIDGenerator.ToAlphaNumerical() };
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
user.UserId.ToString(),
DateTime.Now,
DateTime.Now.AddMinutes(30),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
return new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
}
}
When [Authorize] is fired this code takes care of it:
Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.user_id = serializeModel.user_id;
newUser.username = serializeModel.username;
newUser.roles = serializeModel.roles;
newUser.form_token = serializeModel.form_token;
HttpContext.Current.User = newUser;
}
}
1) Authorize attribute of ASP.NET MVC is totally focused on your first problem. You may even go for customization but not suggested for most of scenarios.
2) To assign the currently logged in user and visible in all the views, you may bind the user name using ViewBag/ViewModel property to Layout(_Layout.cshtml) so that it appears on top of every page where the layout is used.
Note: If you want to perform any pre action-invoke logic, then OnActionExecuting filter is the correct place, before entering that action method.
Precisely, you have to create a class and that class inherits the Controller class.
public class MyAuthentication : Controller
{
public MyAuthentication()
{
isAuthenticated();
}
private void isAuthenticated()
{
// do your authentication
//if user authenticated keep user details within a cookie, so that
// when the next request, program logic will search for cookie,
//if it is found then assume user was authenticated else redirect to login page.
}
}
And then inherits this MyAuthentication class in your project for all controllers
public class DashboardController : MyAuthentication
{
public ActionResult Index()
{
var categories = db.Categories.ToList();
return View(categories);
}
// rest of the codes
}
So that the authentication remains in single place. You can inherit this where ever you want.
If you need current user anywhere in the controller/action then better way is to set the user data when you perform authorization.
In your authorization filter , you can use
System.Web.HttpContext.Current.Items["userdata"]=userDataObject;
For authentication , this article can help.
http://www.dotnet-tricks.com/Tutorial/mvc/G54G220114-Custom-Authentication-and-Authorization-in-ASP.NET-MVC.html
First Put the Form Authentication Cookie when the user is logged in. like
[HttpPost]
public ActionResult Login(Acccount obj){
// check whether the user login is valid or not
if(UseIsValid){
FormsAuthentication.SetAuthCookie(obj.username, obj.RememberMe);
return redirectToAction("Index","DashBoard");
}
return View(obj);
}
*
and Use [Authorize] attribute. like*
[Authorize]
public class DashboardController : Controller
{
private ApplicationContext db = new ApplicationContext();
public ActionResult Index()
{
var categories = db.Categories.ToList();
return View(categories);
}
}
Related
I'm creating a new ASP.NET web application and I'm not planning on making use of the concept of "roles". I do, however, want to make sure a user is logged in on certain pages. Is there any existing attribute that simply checks if a user is logged in and redirects them or throws an error if they're not? Every search I've done points to using roles (such as this one).
The [Authorize] attribute will only return successfully if the user initiating the request is logged in and will only work on controllers and action methods.
It can be used to decorate a particular action:
public class FooController : Controller
{
// only FooAction requires authentication in FooController
[Authorize]
public async Task<ActionResult> FooAction()
{
}
public async Task<ActionResult> BarAction()
{
}
}
...or an entire controller:
// all actions in FooController require authentication
[Authorize]
public class FooController : Controller
{
public async Task<ActionResult> FooAction()
{
}
public async Task<ActionResult> BarAction()
{
}
}
You also have Request.IsAuthenticated which can be used on both action and non-action methods:
if (Request.IsAuthenticated) //or #if in Razor
{
//request is authenticated
}
...and even User.Identity.IsAuthenticated as #Darko correctly pointed out in his answer. Personally, I prefer Request.IsAuthenticated over User.Identity.IsAuthenticated as it also provides some useful null-checks for User and User.Identity. Here's how Request.IsAuthenticated looks under the hood:
public bool IsAuthenticated
{
get
{
return(_context.User != null
&& _context.User.Identity != null
&& _context.User.Identity.IsAuthenticated);
}
}
You can use User property, just put if() where it can control access and that's it.
protected void Page_Load(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
Page.Title = "Home page for " + User.Identity.Name;
}
else
{
Page.Title = "Home page for guest user.";
}
}
This should work after you set the web.config . Here is the documentation https://msdn.microsoft.com/en-us/library/9wff0kyh(v=vs.85).aspx
So my project requirements changed and now I think I need to build my own action filter.
So, this is my current login controller:
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model)
{
string userName = AuthenticateUser(model.UserName, model.Password);
if (!(String.IsNullOrEmpty(userName)))
{
Session["UserName"] = userName;
return View("~/Views/Home/Default.cshtml");
}
else
{
ModelState.AddModelError("", "Invalid Login");
return View("~/Views/Home/Login.cshtml");
}
}
public string AuthenticateUser(string username, string password)
{
if(password.Equals("123")
return "Super"
else
return null;
}
public ActionResult LogOff()
{
Session["UserName"] = null;
//AuthenticationManager.SignOut();
return View("~/Views/Home/Login.cshtml");
}
}
And this is my action filter attempt:
public class AuthorizationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (HttpContext.Current.Session["UserName"] != null)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "MainPage" },
{ "action", "Default" }
});
}
base.OnActionExecuting(filterContext);
}
}
I have already added it to FilterConfig, but when I login it does not load Default.cshtml it just keeps looping the action filter. The action result for it looks like this:
//this is located in the MainPage controller
[AuthorizationFilter]
public ActionResult Default()
{
return View("~/Views/Home/Default.cshtml");
}
So, what would I need to add in order to give authorization so only authenticated users can view the applicationĀ“s pages? Should I use Session variables or is there another/better way of doing this using? I am pretty much stuck with AuthenticateUser(), since what happens there now is just a simple comparison like the one we have there now.
Thank you for your time.
Create an AuthorizeAttribute with your logic in there:
public class AuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true))
{
// Don't check for authorization as AllowAnonymous filter is applied to the action or controller
return;
}
// Check for authorization
if (HttpContext.Current.Session["UserName"] == null)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
As long as you have the Login URL Configured in your Startup.Auth.cs file, it will handle the redirection to the login page for you. If you create a new MVC project it configures this for you:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions {
// YOUR LOGIN PATH
LoginPath = new PathString("/Account/Login")
}
);
}
}
Using this you can decorate your controllers with [AuthorizationFilter] and also [AllowAnonymous] attributes if you want to prevent the authorization from being checked for certain Controllers or Actions.
You might want to check this in different scenarios to ensure it provides tight enough security. ASP.NET MVC provides mechanisms that you can use out of the box for protecting your applications, I'd recommend using those if possible in any situation. I remember someone saying to me, if you're trying to do authentication/security for yourself, you're probably doing it wrong.
Since your attribute is added to the FilterConfig, it will apply to ALL actions. So when you navigate to your MainPage/Default action it will be applying the filter and redirecting you to your MainPage/Default action (and so on...).
You will either need to:
remove it from the FilterConfig and apply it to the appropriate actions / controllers
or add an extra check in the filter so that it doesn't redirect on certain routes
I have a problem with adding AntiForgeryToken. This is my code:
<%:Html.ActionLink("Text", "Action", new { ID = "Hello")})%>
and
RedirectToAction("Action", "Controller", new { ID = "HelloFromMe"});
Controller:
[ValidateAntiForgeryToken]
public ActionResult Action(String ID){
return View();
}
Does anybody have idea how to do it ?
It is impossible to use an AntiForgeryToken into a GET method.
GET methods should only be used for read-only operation on your server. If you want to do something else than a read-only operation, then, you should use a POST method.
Here the reason why this token is useful, how and when to use it. http://haacked.com/archive/2009/04/02/anatomy-of-csrf-attack.aspx
The idea of antiforgery token is to prevent attacker to generate POST / GET request on behalf of the user. Thats why we add something special to every POST / GET request, that is unknown to attacker.
The simplest implementation of custom antiforgery would look like this.
And it will be exactly safe as ValidateAntiForgeryToken.
public class ProfileController : AuthorizedAccessController
{
// GET
public ActionResult Details(int userId)
{
User user = this.Entities.User.First(u => u.Id == userId);
this.Session["ProfilePageAntiforgery"] = Guid.NewGuid(); // use RandomNumberGenerator to generate strong token
this.ViewBag.ProfilePageAntiforgery = this.Session["ProfilePageAntiforgery"];
return View(user);
}
public ActionResult DeleteMyProfile(int userId, string profilePageAntiforgery)
{
if ((string)this.Session["ProfilePageAntiforgery"] != profilePageAntiforgery)
{
return this.RedirectToAction("Details", new { userId });
}
User user = this.Entities.User.First(u => u.Id == userId);
this.Entities.User.Remove(user);
this.Entities.SaveChanges();
return this.RedirectToAction("ProfileDeleted");
}
}
View:
<div>
#Html.ActionLink("Delete my profile", "DeleteMyProfile", new {userId = this.Model.Id, profilePageAntiforgery = this.ViewBag.ProfilePageAntiforgery })
</div>
Making custom attributes out of this is a matter of technique.
Lets say one attribute to store token in session and viewbag and the other to validate.
If using Session is not OK (Web farm etc.) you can simply replace it by DB or other store.
I need authorization attribute to ensure that authenticated user can access in secure area to manage his and only his data.
I was thinking to retrieve httpContext.User.Identity.Name and to match that to retrieved username from the database.
Is this basically good (secure) approach? or it can be done with better secure on the mind?
So, my controller is decorated with custom attribute
[UserAccountAuthorizeAttribute]
public ActionResult Edit(string username)
{
return View();
}
and I'm overriding AuthorizeAttribute but in the debug mode got null value on following line
var rd = httpContext.Request.RequestContext.RouteData;
var username = rd.Values["username"]; // null
what can be aproblem ?
You shouldn't be putting the username in the querystring.
If you're using the built in ASP.NET Membership provider your action would look something like this.
[Authorize]
public ActionResult Edit()
{
var userId = (Guid)Membership.GetUser().ProviderUserKey;
var someService = new MyService();
var someData = someService.GetSomeDataByUserId(userId);
//...
}
I have a controller method called Edit in which the user can edit data they had created like so ...
public ActionResult Edit(int id)
{
Submission submission = unit.SubmissionRepository.GetByID(id);
User user = unit.UserRepository.GetByUsername(User.Identity.Name);
//Make sure the submission belongs to the user
if (submission.UserID != user.UserID)
{
throw new SecurityException("Unauthorized access!");
}
//Carry out method
}
This method works fine however it is a little messy to put in every controller Edit method. Each table always has a UserID so I was wondering if there was an easier way to automate this via an [Authorize] Attribute or some other mechanism to make the code cleaner.
Yes, you could achieve that through a custom Authorize attribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
var rd = httpContext.Request.RequestContext.RouteData;
var id = rd.Values["id"];
var userName = httpContext.User.Identity.Name;
Submission submission = unit.SubmissionRepository.GetByID(id);
User user = unit.UserRepository.GetByUsername(userName);
return submission.UserID == user.UserID;
}
}
and then:
[MyAuthorize]
public ActionResult Edit(int id)
{
// Carry out method
}
and let's suppose that you need to feed this submission instance that we fetched into the custom attribute as action parameter to avoid hitting the database once again you could do the following:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
var rd = httpContext.Request.RequestContext.RouteData;
var id = rd.Values["id"];
var userName = httpContext.User.Identity.Name;
Submission submission = unit.SubmissionRepository.GetByID(id);
User user = unit.UserRepository.GetByUsername(userName);
rd.Values["model"] = submission;
return submission.UserID == user.UserID;
}
}
and then:
[MyAuthorize]
public ActionResult Edit(Submission model)
{
// Carry out method
}
I would suggest you pull the logic out of the action/controller and build a domain class to handle that logic.
Action methods should really only deal with getting data from and sending data to the view. You could create something generic enough to handle your needs but will also follow the single responsibility principal.
public class AuthorizedToEdit
{
protected override bool AuthorizeCore(string user, int itemId)
{
var userName = httpContext.User.Identity.Name;
var authUsers = SubmissionRepository.GetAuthoriedUsers(itemId);
return authUsers.Contains(user);
}
}
This would also allow you to have the flexibility later on to allow something like admin users
#if (Request.IsAuthenticated && User.IsInRole("Student"))
{
#Html.ActionLink("Edit", "Edit", new { id = item.StdID })
}
in my case, the loggedIn user is a student. so i say if the login request is authenticated, and if his role is student, then let the link for edit be accessible to him.
this below allows you to let the ordinary user OR the Admin perform edit also.
#if(Request.IsAuthenticated && User.IsInRole("Student") ||
User.IsInRole("Administrator"))
{
#Html.ActionLink("Edit", "Edit", new { id = item.StdID })
}
I recommend reading up on the AuthorizeAttribute (see here). Also, have you seen this post? It goes over how to override the authentication attribute innards and how to use IPrincipal and IIdentity.