I am facing an issue in MVC that i am able to visit the previous page on browser back button click even after getting logged out. I have few approaches:
1) Disable the Browser back button using window.history.forward().
This will give bad user experience.
2) Using outputCacheAttribute by providing the duration=0 but this will restrict both server side and client side caching. SO don't want to use this.
3) Adding below method in global.asax.cs
protected void Application_BeginRequest()
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
}
Third approach will not allow to make the copy of the cache to the browser. Also to make this work I have to add the [Authorize] attribute on each controller.
This is not the best option for me as I have hundreds of controller. And tomorrow if I will add new controller then again I have to decorate the Authorize attribute to that new controller.
Is there any other approach that any one of you can suggest.
You can only add this attribute:
[OutputCache(NoStore = true, Duration = 0, Location = OutputCacheLocation.None)]
Public ActionResult SomeAction()
{
//
}
Disabling cache in specific action should be enough I guess.
Or if you still do not want to destroy cache just couple of places,
you can do that in your LOGIN function, you can add previous Attribute, or just use this when someone signs out:
Session.Clear();
Session.Abandon();
I wish it will help, since I havent got much time on testing it myself.
Related
The [Authorize] is wonderful for locking pages down but I am building a new product with few users and it makes no sense that it directs people to Login, because there is no one to login yet. It should direct them to Register instead.
But I am struggling to find an easy way to do that without a ton of middleware.
You can change the LoginPath on start up, but I suspect this does not answer your question because when enough users exists then what happens?
To change the login path you can add:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(cookieOptions =>
{
cookieOptions.LoginPath = "/register";
cookieOptions.AccessDeniedPath = "/account/denied";
cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(120);
});
However if you are wanting a switch when you have reached a critical mass then this will not suffice.
EDIT one approach could be something like:
Create a loginOrRegister page. Then on this page hit the database (or whatever you use to see whether you have hit critical mass or its a known user based on a cookie) and then either
Redirect to Login
OR
Rediect to register
I have an Angular app that integrates with IdentityServer4 with implicit flow and the angular-oauth2-oidc library.
Everything seems to work fine, I can log in; and access token is available.
If I click the logout button, I handle it like:
logout() {
this.oauthService.logOut();
}
...and I'm redirected to Identity Server, where it asks me if I really want to log out.
My question is whether I can bypass that prompt? I mean, I want to log out completely if the button is clicked and redirected back to the Angular site, without the need to confirm it?
How can this be achieved?
EDIT: as mentioned in the other answers, it should work if you pass id_token_hint. So I did:
logout() {
this.oauthService.customQueryParams = {
'id_token_hint': this.oauthService.getIdToken()
};
this.oauthService.logOut();
}
But it doesn't make any difference.
There were two issues I needed to fix in order to make this work.
In IdentityServer, AccountOptions class, I had to set this property to true instead of false:
public static bool AutomaticRedirectAfterSignOut = true;
Next, In IdentityServer client configuration, I had to define the post logout redirect uri:
PostLogoutRedirectUris = new List<string> {"http://...."}
That did it. I did not have to change anything in the Angular client.
I am using asp.net mvc3 to develop application.
I protect my pages agaings CSRF by aniforgerytoken.
Imagine that i have Delete button above the datagrid. If user clicks on button, ajax call will post id's of selected items to e.g Countries/Delete action.
Problem is, that user can try to change request's id's (when he look into source code page, he will se $.post(...)), so he can delete countries, which was not selected (in the worst case, for which he hasn't privileges to delete them)
How can i protect my pages againts this? I don't wanna check on delete action, if user really has rights to delete items.
I hear about some ajax call hashing, but didn't found any useful tutorials or something to do that in MVC.
I don't think you need to do anything on the client side. Instead, you should check on the server side that the user has privileges to delete the countries that were selected for deletion, and only delete those that they have privileges to delete.
If you are using a post use the below
[Authorize]
[ValidateAntiForgeryToken]
If you are using a get use
[Authorize]
You can also use this custom attribute below
public class HttpAjaxRequestAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (!controllerContext.HttpContext.Request.IsAjaxRequest())
{
throw new Exception("This action " + methodInfo.Name + " can only be called via an Ajax request");
}
return true;
}
}
Then, decorate your action as below
[Authorize]
[HttpAjaxRequest]
public ActionResult FillCity(int State)
{
//code here
}
Remember to "Mark/Tick" if this solve your problem.
How can I clear browser cache only on logout, sure I can use the below:
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
But this particular page which is a shopping bag page is accessible by both login and non-login users. How can I set it in such a way whereby the login user is able to access this page without clearing the browser cache but Only clears it when he/she logs out so that another user will not be able to access the history contents.
I have tried the solutions here:
http://www.codeproject.com/Tips/135121/Browser-back-button-issue-after-logout
made some changes but still couldn't figure out how to deal with this issue.
I also cleared my session on logout as below but I understand the browser cache will still stay.
FormsAuthentication.SignOut();
Session.Abandon();
Response.Redirect("~/");
Please advice. Thanks.
I am not a c# expert, but I am pretty sure what you have above only tells the browser to not cache the page you are on. There is no way to tell the browser to clear cache on any page. This would be a problem if there was such a way. Sounds like the solution you need is to not cache any page at all, regardless of logging out or not.
Perhaps you are getting muddled with the difference between server and client cache?
If you set output cache on your aspx page, that's server-side cache, and you have a scenario where .NET can decide whether to send pre-cached content or not, and still apply ACL rules.
If you set cache requirements on the HTTP you return using Response.Cache, that's client-side caching. Once the browser obeys the cache rules you send here, the only opportunity you will have to retract your cache rules is the next time the browser requests the page. If you set the cache to expire tomorrow, that's the next chance you'll get to amend the caching. Assuming the browser is obeying you, by the way, of which there is no guarantee.
In short, dynamic pages should not attempt to set client-side caching if you want them to stay dynamic. In fact you should actively use techniques such as the ones you mentioned to suppress Caching on those pages at all times.
Client-side caching should really only be used to assist with performance and bandwidth on the static parts of your site.
I am trying to solve a similar problem myself. This is just speculation, but if i could track a user specific header in my requests I was going to try using
HttpContext.Current.Response.Cache.VaryByHeaders["login"] = true;
in the global.asax
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg == "login")
{
return User().Name;
}
return base.GetVaryByCustomString(context, arg);
}
There is a way to do it. If you are caching a page, you can add a vary parameter. For Example
[OutputCache(Duration = 60, Location = System.Web.UI.OutputCacheLocation.Client, VaryByParam = "random")]
[CompressFilter]
public ActionResult Page(PageModel model)
{
...
}
In the example above, if I pass a random variable like the ticks of the current datetime object, that will prevent the cache.
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.