I'm developing an aspnet mvc application and i have a problem to reload some data, i don't know if the problem is in the security principal info that every view contains or in my partial view that doesn't reload when some other view is shown.
I have a layout page that is used in my whole application, and inside this layout page i have this partial view code fragment:
#Html.Partial("_PartialActiveClient")
this partial view contains the next lines:
#if (User.IsInRole("HasClients"))
{
<p>An Active Client</p>
Change active client
}
else
{
<p>You don't have any clients<p>
Create client
}
So when a user creates an account for the first time, this user has 0 clients and doesn't belong to the "HasClients" role and the "you don't have any clients" message is shown, but when this user creates his first client, the user is added to the "HasClients" role by default in my controller. The problem is that the "you don't have any clients" is still there even if i change the views. In order for the other message to show, this user has to log out and then log in again.
So my question is, how can my users see the "An Active Client" message immediately after they create a client?
The roles are cached in a cookie , so you can force them to refresh by deleting the cookie. You need to refresh users role. Here is a ways
you can call this Roles.DeleteCookie(); and it will clear roles and reload it in the next request.
This is taken from this question page Refresh ASP.NET Role Provider
Just to clarify my comment to the answer provided by Tchaps. This is the code (not my original code, but make my point) to my solution, like i wrote in the comment, it's not elegant but i works, at least in my case.
// Add to role, save changes
await _userManager.AddToRoleAsync(userInSession.Id, roleHaveClients.Name);
await _userManager.UpdateAsync(userInSession);
// Sign out / Sing in
_registerManager.AuthenticationManager.SignOut();
await __registerManager.SignInAsync(userInSession, false, false);
// Redirect to another action
return RedirectToAction("someAction","someController");
Related
We faced following problem. We developed set of post-login pages for our system. And users could have one of 5 roles: Free User Type 1, Premium User Type 1, Free User Type 2, Premium User Type 2 and Admin.
The problem is that even though all pages for every of these roles should look almost the same but it it is still a bit different depending on user role (for example links point to different URLs, or different modal are shown when buttons are clicked, some options are shown and some are hidden).
What we are trying to do is to wrap this all in small partial views and render different partial views depending on user role. But it gets more and more complex.
Maybe there is some kind of design pattern or general approach to deal with this problem?
Thanks!
Seems based on your description which is not 100% clear that the best approach would be to create the different partial views for the different roles.
Then on login get the user role from the DB, and return the different partial views based on the role.
If you have multiple partial views i.e. more than one page per user role, you can add the user role to a session or a cookie so you do not have to hit the DB again.
Id recommend using the cookie approach if this is the case.
Set Session
var userRole = 1;
Session["UserRole"] = userRole;
Get Session:
var userRole = Session["UserRole"] as int?;
Set Cookie
var cookie = new HttpCookie("UserRole");
cookie.Value = GetUserRole();
cookie.Expires = DateTime.Now.AddDays(1);
HttpContext.Current.Response.Cookies.Add(cookie);
Get Cookie
if (HttpContext.Current.Request.Cookies["UserRole"] != null)
{
var userRole = HttpContext.Current.Request.Cookies["UserRole"].Value);
}
In my application I am using Forms-Authentication to sign in and sign out users.
One functionality is admin can change the username of other users. In that case, I need to sign out the user whose username is changed.
If I do not, due to their cookies set before, they gain access to application and receive error messages (since their username does not exist and there are parts where I use their username for some functionality).
How can I force these users to log out using Forms-Authentication ?
UPDATE :
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controller = filterContext.RouteData.Values["controller"].ToString();
string action = filterContext.RouteData.Values["action"].ToString(); ;
// Below returns the previous username, which does not exist anymore in db.
string userName = HttpContext.Current.User.Identity.Name;
UnitOfWork unitOfWork = new UnitOfWork();
if (!unitOfWork.UserRepository.UserExists(userName))
{
FormsAuthentication.SignOut();
filterContext.HttpContext.Session.Clear();
filterContext.HttpContext.Session.Abandon();
// I am not using Roles.
}
unitOfWork.Dispose();
base.OnActionExecuting(filterContext);
}
In my customer global filter, I check whether user exist or not, if not I sign them out. However, it is not working. By working I mean they pass the authentication and gain access to application.
Thanks in advance.
Here's what you do to force user to sign out:
public void UserPasswordChangedHandler()
{
FormsAuthentication.SignOut();
Roles.DeleteCookie();
Session.Clear();
}
I don't think line by line explanation required, its self explanatory enough.
Please let me know if I am mistaken.
Update
Straightforward answer to your additional question is to keep per user boolean tracking if his data was updated by admin and if yes - just redirect him to login page.
Please see following articles for forced logout using forms authentication information:
How can I force a user to log out
How can I force a log out of all users for a website,
ASP.NET forms authentication forced logout
How to log off multiple MembershipUsers that are not the current user
Update 2
Clearing cookies
HowTo: create and remove Cookies with ASP.NET MVC
How do you clear cookies in ASP NET MVC 3 and C#
How do I invalidate a bad authentication cookie
Hope this help you.
When a user needs to become invalidated you must add their details to some kind of internal static list.
Then on every page request (possibly using Application_BeginRequest) see if that current user is in that list, and if so to call FormsAuthentication.SignOut there-and-then.
It seems like a bit of a hack, but it's the best I can think of right now.
Note that removing a user-in-absentia's session state is another issue entirely.
I'm new to MVC and I would like to get suggestions on how to best handle action based permissions in my application.
I currently have some global permissions being checked at the controller level which work fine for rendering views the current user has access to, etc.
However, once the view has been rendered, I want to make decisions such as 'enable DELETE button, ONLY IF user has delete permissions for the item currently selected' At that point, those permissions are no longer Global but based on the context of the object selected.
How should I write my code to handle this type of scenario?
By Default your Views have access to the User Object.
You can check on the View if User.IsInRole("myDeleteRole").
or
#if(User.IsInRole("MyDeleteRole"))
{
<input type="subtmt" value="Delete">
}
I don't know if this is the best way, but its what i have done in the past
I guess another way would be to write seperate Views depending on what rights a user has. that way you could do the logic on the controller and send the user to the specified view
if(User.IsInRole("MyDeleteRole")
{
return View("MyDeleteView", vm)
}
else
{
return View("NoDeleteView", vm)
}
I have a purchase page and I don't want the user to be able to refresh the page and resubmit the form once they get to the 'order complete' page because it automatically sets them up in our system via database values and charges their card via paypal (only want these to happen ONCE)... I have seen some sites that say 'Don't hit refresh or you will get charged twice!' but that is pretty lame to leave it open to possibility, what's a good way to only allow it to be submitted once or prevent them from refreshing, etc?
PS: I saw a few similar questions: PHP: Stop a Form from being accidentally reprocessed when Back is pressed and How do I stop the Back and Refresh buttons from resubmitting my form? but found no satisfactory answer... an ASP.NET MVC specific answer would be ideal too if there is a mechanism for this.
EDIT: Once they click submit it POSTS to my controller and then the controller does some magic and then returns a view with an order complete message, but if I click refresh on my browser it does the whole 'do you want to resend this form?' that is bad...
The standard solution to this is the POST/REDIRECT/GET pattern. This pattern can be implemented using pretty much any web development platform. You would typically:
Validate submission after POST
if it fails re-render the original entry form with validation errors displayed
if it succeeds, REDIRECT to a confirmation page, or page where you re-display the input - this is the GET part
since the last action was a GET, if the user refreshes at this point, there is no form re-submission to occur.
I 100% agree with RedFilter's generic answer, but wanted to post some relevant code for ASP.NET MVC specifically.
You can use the Post/Redirect/Get (PRG) Pattern to solve the double postback problem.
Here's an graphical illustration of the problem:
What happens is when the user hits refresh, the browser attempts to resubmit the last request it made. If the last request was a post, the browser will attempt to do that.
Most browsers know that this isn't typically what the user wants to do, so will automatically ask:
Chrome -
The page that you're looking for used information that you entered.
Returning to that page might cause any action you took to be repeated.
Do you want to continue?
Firefox - To display this page, Firefox must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
Safari -
Are you sure you want to send a form again?
To reopen this page Safari must resend a form. This might result in duplicate purchases, comments, or other actions.
Internet Explorer -
To display the webpage again, the web browser needs to
resend the information you've previously submitted.
If you were making a purchase, you should click Cancel to
avoid a duplicate transaction. Otherwise, click Retry to display
the webpage again.
But the PRG pattern helps avoid this altogether by sending the client a redirect message so when the page finally appears, the last request the browser executed was a GET request for the new resource.
Here's a great article on PRG that provides an implementation of the pattern for MVC. It's important to note that you only want to resort to a redirect when an non-idempotent action is performed on the server. In other words, if you have a valid model and have actually persisted the data in some way, then it's important to ensure the request isn't accidentally submitted twice. But if the model is invalid, the current page and model should be returned so the user can make any necessary modifications.
Here's an example Controller:
[HttpGet]
public ActionResult Edit(int id) {
var model = new EditModel();
//...
return View(model);
}
[HttpPost]
public ActionResult Edit(EditModel model) {
if (ModelState.IsValid) {
product = repository.SaveOrUpdate(model);
return RedirectToAction("Details", new { id = product.Id });
}
return View(model);
}
[HttpGet]
public ActionResult Details(int id) {
var model = new DetailModel();
//...
return View(model);
}
While serving up the order confirmation page you can set a token that you also store in the DB/Cache. At the first instance of order confirmation, check for this token's existence and clear the token. If implemented with thread safety, you will not be able to submit the order twice.
This is just one of the many approaches possible.
Note that the PRG pattern does not completely guard against multiple form submissions, as multiple post requests can be fired off even before a single redirect has taken place - this can lead to your form submissions not being idempotent.
Do take note of the answer that has been provided here, which provides a workaround to this issue, which I quote here for convenience:
If you make use of a hidden anti-forgery token in your form (as you
should), you can cache the anti-forgery token on first submit and
remove the token from cache if required, or expire the cached entry
after set amount of time.
You will then be able to check with each request against the cache
whether the specific form has been submitted and reject it if it has.
You don't need to generate your own GUID as this is already being done
when generating the anti-forgery token.
Give each visitor's form a unique ID when the page is first loaded. Note the ID when the form is submitted. Once a form has been submitted with that ID, don't allow any further requests using it. If they click refresh, the same ID will be sent.
Simply do a redirect from the page that does all the nasty stuff to the "Thank you for your order" page. Having done that, the user can hit refresh as many times as he likes.
If you doesn't like redirect the user to other page, then by using my way you dose not need Post/Redirect/Get (PRG) Pattern and the user remain on the current page without fear of the negative effects of re-submitting of the form!
I use a TempData item and a Hidden field (a property in the ViewModel of the form) to keep a same Guid in both sides (Server/Client) and it is my sign to detect if the form is Resubmitting by refresh or not.
Final face of the codes looks like very short and simple:
Action:
[HttpPost]
public virtual ActionResult Order(OrderViewModel vModel)
{
if (this.IsResubmit(vModel)) // << Check Resubmit
{
ViewBag.ErrorMsg = "Form is Resubmitting";
}
else
{
// .... Post codes here without any changes...
}
this.PreventResubmit(vModel);// << Fill TempData & ViewModel PreventResubmit Property
return View(vModel)
}
In View:
#if (!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
<div>ViewBag.ErrorMsg</div>
}
#using (Html.BeginForm(...)){
#Html.HiddenFor(x=>x.PreventResubmit) // << Put this Hidden Field in the form
// Others codes of the form without any changes
}
In View Model:
public class OrderViewModel: NoResubmitAbstract // << Inherit from NoResubmitAbstract
{
// Without any changes!
}
What do you think?
I make it simple by writing 2 class:
NoResubmitAbstract abstract class
ControllerExtentions static class (An Extension class for System.Web.Mvc.ControllerBase)
ControllerExtentions:
public static class ControllerExtentions
{
[NonAction]
public static bool IsResubmit (this System.Web.Mvc.ControllerBase controller, NoResubmitAbstract vModel)
{
return (Guid)controller.TempData["PreventResubmit"]!= vModel.PreventResubmit;
}
[NonAction]
public static void PreventResubmit(this System.Web.Mvc.ControllerBase controller, params NoResubmitAbstract[] vModels)
{
var preventResubmitGuid = Guid.NewGuid();
controller.TempData["PreventResubmit"] = preventResubmitGuid ;
foreach (var vm in vModels)
{
vm.SetPreventResubmit(preventResubmitGuid);
}
}
}
NoResubmitAbstract:
public abstract class NoResubmitAbstract
{
public Guid PreventResubmit { get; set; }
public void SetPreventResubmit(Guid prs)
{
PreventResubmit = prs;
}
}
Just put them in your MVC project and run it... ;)
Off the top of my head, generate a System.Guid in a hidden field on the GET request of the page and associate it with your checkout/payment. Simply check for it and display a message saying 'Payment already processed.' or such.
Kazi Manzur Rashid wrote about this (together with other asp.net mvc best-practices). He suggests using two filters to handle data transfer between the POST and the follwing GET using TempData.
I have two pages, NonMember.aspx and Member.aspx. If a user comes to the site, they will go to NonMember.aspx as soon as they login, I want them to immediately be redirected to Member.aspx, but instead of doing this, it is staying on NonMember.aspx. The user actually has to go click on the menu item again to get to Member.aspx.
The links are located at http://abc.com/tools/NonMember.aspx
and http://abc.com/tools/Member.aspx.
I was doing:
System.IO.Path.GetFileNameWithoutExtension(Request.Url.ToString());
but I am thinking there is a better way, especially since I have multiple default.aspx pages and this could pose a problem
Here is more detail on what exactly I am doing:
When I run the site on my local development machine, the NonMember page points to:
http://testserver/tools/NonMember.aspx.
Requet.Url.AbsolutePath points to /testserver/tools/NonMember.aspx.
Then I am doing this:
if(url == "~/tools/NonMember.aspx")
{
Response.Redirect("~/tools/Member.aspx");
}
The above is not working and I can check if url is equal to /testserver/tools/NonMember.aspx because if I deploy to liveserver, it will fail.
When using Forms Authentication for an ASP.NET application, it will automatically redirect you to the page you were viewing before you logged in. This is why you are redirected back to the NonMember.aspx page.
It would be better if you had just one member page, and perform a check in the page to see if the user is authenticated, if so, display the member content, otherwise, display the non-member content.
Then, when the user logs in and is redirected back to the page, they will see the member content.
If you are insistent on keeping the two separate pages, then in your check you simply have to see if the current user is authenticated (through the IsAuthenticated property on the User that is exposed through the page) and then redirect to your members page. If you are on the NonMember page, you don't need to check to see what the url is (unless this is MVC, which you didn't indicate).
If you have the HttpResponse object, you can use HttpResponse.Redirect
You should check your cookie or session variable to see if the user is logged in and then use a Response.Redirect to move them to the member page.
I'm not sure I get your scenario but, let's try.
If you have public pages, the login page and private pages (member pages) and once you authenticate your users you want them to browse ONLY the private part of your website, you should check early in the processing steps (AuthorizeRequest would be a good phase) if the request comming in is for a public or for the login page which you also consider public and redirect to your private page from there (like...having a "home" page for the member area of the site and always redirecting to it once you get an authenticated and properly authorized request to some public URL of your site).
EDIT: You should check for Request.FilePath
I ended up just doing the following:
if(Authenticated)
{
string path = Request.UrlReferrer.AbsolutePath;
if (path.EndsWith("/tools/NonMember.aspx"))
{
Response.Redirect("~/tools/Member.aspx");
}
}