Preventing Re-submission of data on Browser Refresh - asp.net

I have a form. On submit of a button, it inserts a record into the db. If I go back in my browser and refresh the page it's resubmitting it causing all sorts of issues as you can imagine. How do I ensure that the user can't refresh the previous page and the state of that page does another automatic submit on refresh like this?
I'm not using ViewState. I actually have it disabled in the page directive
No, Response.Redirect does NOT solve this problem. The user can still go back with the browser's back button and refresh at the point where they submitted and it will still run the button's event sending the data downstream to my DL insert

The solution I use most often is this:
When the user first visits the page (Non-PostBack), generate a token value (GUID is easy). Take this token value and store it in their Session variables and a hidden field in the page. Being a field, this value should persist through round trips to the server without ViewState enabled (I could be mistaken on that count, so please check it!). Should the page be refreshed (and form values lost), a new value will be generated by the Non-PostBack initialization.
When the page is posted back, retrieve the hidden field value and compare it against the expected value in the user's Session variables:
If the tokens match, accept the submission as "genuine", remove the token from the Session variables, and continue your workflow.
If the token is missing from Session variables, the user is trying to submit the form again. - If the tokens do not match, the user is "replaying" an old submission of the form.
Example code to achieve this sort of solution:
public partial class MyPage : Page
{
protected HiddenField tokenField;
protected void Page_Load()
{
if(!IsPostBack)
CreateToken();
}
// Call this method to establish a token in session and on the page.
private void CreateToken()
{
string token = new Guid().ToString();
Session["dupeToken"] = token;
tokenField.Value = token;
}
// Call this method to validate the token before continuing workflow.
private bool TokenIsValid()
{
string expectedToken = (string)Session["dupeToken"];
if(expectedToken == null)
return false;
string actualToken = tokenField.Value;
return expectedToken == actualToken;
}
// Call this method when the page submission is complete to prevent re-submission.
private void ConsumeToken()
{
Session["dupeToken"] = null;
}
}

The easiest way would be to wrap the form in an < asp:UpdatePanel>. That way, all postbacks are done via AJAX and the browser will never ask you to re-submit your form.

The best two ways of doing this are:
Performing a check on a distinct field against the database
Create a hidden token in the form that uses a salt based on time. If you put logic in script to check for the existing time and compare it to the token, you can either allow the submission or define it. For example, if the form is drawn at a certain time, you store the token, and within 30-60 seconds you can submit, but after that you cannot.

Related

How can i do custom authorization in my ASP.NET MVC application

I am making a small SAAS application. I am using the same database for all users and only differentiating between the data using username and ids. This means that the user can type in a new url in the browser and see other users data. This, of course is not a desirable approach. I would like to do a check to see if the current user can actually access the resources: eg.
http://myapplication.com/images/15
And if the user changes the url to
http://myapplication.com/images/16
I should do a check in my database to see if the current user actually has access to see the images with user id 16. And if not redirect to a "not authorized" page.
How do I implement this?
The first step is to make sure that you never have any ID's for the user itself in the url. For instance, never have http://example.com/?user=10. You should always get the users id from their authentication rather than from the URL (or posted values either).
The second step, is to use that ID in your queries. So, for instance, let's say they seek http://example.com/images/100, then in your database you should have a mechanism that links the asset's ownership to the user, either a userid or a mapping table of id's to asset's, etc.. This way, if the user isn't allowed access, it will just return an empty result set. It's impossible for the data to be returned, and the empty result set should tell your page that the item doesn't exist (not necessarily an authorization failure, just that the object doesn't exist).
Third, any pages which are inherently about the user, such as a user profile, account page, or dashboard should never have any ID's at all in the URL, it should just automatically go to the authenticated users page.
Finally, if you need to prevent the user from accessing an entire page or set of pages, then you should do this in the OnAuthorization event or similar (custom attribute, base class, etc..) or using the built-in attribute authorization and use role based authorization. Never do authorization in the PageLoad or similar event (such as the controller action), because by the time you get to that step a lot of work has already happened in the pipeline. It's best to block access long before the page even starts to setup. Authorization events happen at the very beginning of the pipeline.
Make an Action that check userId and returns error page or file
public FileResult Image(string imageName)
{
string UserId = MethodWhereYouGetCurrentUserID();
if(imageName == null) return View("~/Views/Shared/Error.cshtml", (object)"Null image");
string imageShortName = imageName.Split(".")[0];
if(!UserId == imageShortName) return View(~/Views/Shared/Error.cshtml, (object)"You can't access to this");
string path = Server.MapPath("~/Contant/images/"+imageName);
return File(path, "image/jpg");
}
RouteConfig file
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute
(
name: "ImageRoute",
url: "/images/imageName",
default: new {controller = "Home", action = "GetImage"}
);
}

Dealing with an empty Session in asp.net

I have a question about empty session and "try-catch" handling.
I have few forms that use a session to pass data one to each other and sometime when the user is stay for a long time on specific page he cannot continue to the next page because the session is empty.
I want to handle it somehow but i have no idea.
On page load of the next page, check if the user has valid session, if not then redirect to the login page.
Suppose you store user's login id in LoginID field of the session, then you can check as following
if(Session["LoginID"]==null || Convert.ToString(Session["LoginID"])==string.Empty)
{
//Redirect to login page
}
Session is a collection.
You can use its Count property.
if(Session.Count == 0)
{
// session is empty
}

ASP.NET session key for web service security?

I've got a web service (ASP.NET 2.0) that I'm calling from javascript using jQuery's $.ajax(). I've been told that the session key is often used as a nonce in a situation like this for security purposes; the web service takes a key as one of its parameters and only returns data if it matches the current session key.
I'm attempting to accomplish this by setting the value of a hidden field to the current SessionID on every Page_Load (i.e. every postback), then grabbing it in the javascript to pass as a parameter. But it's never the same key as the web service's current key (Context.Session.SessionID).
Is this possible to resolve, or should I be doing this another way?
EDIT: code to set session in hidden field as requested.
hfSession.Value = Context.Session.SessionID;
That's in the Page_Load of a .ascx control, not under any conditional (i.e. not wrapped with if (!Page.IsPostBack).
I believe you are trying to prevent Cross Site Script Request Forgery (CSRF). The Session ID is actually sent across as a cookie and the attacker can set this. Rather than use the Session ID itself, you should use a randomly generated number stored in a Session variable.
String GetCSRFToken()
{
String token = (String)Session["CSRFToken"];
if( token == null )
{
token = GenerateLongRandomString();
Session["CSRFToken"] = token;
}
return token;
}
void AssertValidCSRFToken(String token)
{
if( token != GetCSRFToken() )
{
throw new Exception("Invalid Request!")
}
}
Here is a link to another question with more info on preventing this kind of attack:
CSRF Validation Token: session id safe?
Asp.net actually generates a new Session ID for every request until you use the Session State to store some value. This could be a reason why the values are different. Try and save something in the session. Perhaps
Session["SessionID"] = Context.Session.SessionID;
hfSession.Value = Context.Session.SessionID;
A new SessionID is generated each time the page loads until the session is actually allocated. So if you don't actually allocate anything to the session, the SessionID will change upon each page load.

Why not implement 'IsRefresh', 'IsBackPost' just like is 'IsPostBack'?

I could see, myself and many people are having trouble with this two items in ASP.NET... Refresh Button, Back Button... (I see many people went to the exent of asking how to disable these two buttons in the browser..)
Now what are the problems in Implementing two more boolean variables in Page (IsRefresh, IsPostBack)... If those problems can be circumvened and implemented, it would be a great asset to developers...
When you are answering, if you could also include the steps you are taking in your web app in order to avoid reposting (at times in DB) in such scenarios that would be helpful.
Thanks
The problem with implement the two additional boolean properties is, that there really is no (reliable) way to distinguish Back / Refresh triggered requests. When hitting on of these buttons, the browser will either:
display the page from the cache if allowed, or
execute the exact same request again (possibly asking the user to confirm first)
You are experiencing case #2. When the second request occurs, the server will recieve the exact same request data as it did with the original request. Thus, ASP.NET (and most other frameworks) will handle the request just like the original was handled. There are several ways to work around this problem:
Change your caching policy to allow the browser to redisplay the results of previous requests (might solve the Back problem, but not Refresh).
Add a hidden field (or data in the ViewState) containing a unique value on the page from where postbacks are expected. Then add some data structure to keep a list of "used" values, along with logic to test for duplicates before doing anything critical.
Use the Post/Redirect/Get pattern to ensure that the POST request never ends up in the browser history.
Solution #3 is dead easy, and works in almost every case. Basically, rather then returning the results/confirmation as a reply to the "critical" postback, do a redirect to a new URL:
protected void btnCritical_Click(object sender, EventArgs e)
{
DoSomethingThatShouldNotBeDoneTwice();
Response.Redirect("confirmation.aspx");
}
aspnet_isapi recognizes a postback from the form content, specifically the ViewState field. It has no intrinsic way to distinguish a postback from a refresh.
i have seen a few strategies for working with this in the past. One is to assign each request a guid and use that as a unique key or index in the table you are writing to.
You can create a base page that all of your pages inherit from.
public partial class PageBase : System.Web.UI.Page
{
private Guid _requestId;
protected Guid RequestId
{
get
{
return _requestId;
}
}
protected virtual void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
_requestId = Guid.NewGuid();
ViewState.Add("requestId", _requestId);
}
else
{
_requestId = (Guid)ViewState["requestId"];
}
}
}
public partial class _Default : PageBase
{
protected override void Page_Load(object sender, EventArgs e)
{
base.Page_Load(sender, e);
// now do stuff
}
}
It's not quite that clear cut. The only way the server can identify a "PostBack" is based on the headers passed to the page by your request. When you POST a form, "IsPostBack" is true, as you would expect. When you refresh a page or press Back the exact same request data is sent. There's no way for the application to detect that the user issued a non-standard request (Back or Refresh) without changing the behavior of the browser that sends the request.
As you know, most browsers will indicate to you that "Clicking Back will resend the form data...", but that is merely a warning issued by the browser to let you know that it is going to send the exact same request over again. The server does not know this and has no (native) way to interpret the information.
Double Postback Prevention Tips
One way to prevent data getting posted twice is to ensure that each PostBack contains some unique data that you can validate. The process is fairly simple.
On each page load you will want to create a unique identifier for that page's PostBack events. It doesn't matter what the unique id is, so long as it isn't the same on sequential page loads. Place that unique identifier in a hidden field on your page AND in the user's session (or a cookie). Then, on each PostBack validate that the cookie in the hidden field matches the value in the session. If the values match the PostBack is an original post to the page which can be processed accordingly. After performing the necessary operations you will then need to change the unique identifier in both locations again. This way, if the user should hit back and choose to "Resend Data", the hidden field will not match the session key and you can throw out the duplicate post data.

ASP.NET: How to redirect, prefilling form data?

i want a handler to redirect to a web-forms page, pre-filling in the values of some controls on the form.
i tried setting my current Request.Form data:
if (theyWantToDoSomething)
{
//pre-fill form values
context.Request.Form["TextBox1"] = "test";
context.Request.Form["ComboBox1"] = "test 2";
context.Request.Form["TextBox2"] = GetTheTextForTheThing();
//tell the client to go there
context.Response.Redirect("~/SomeWebForm.aspx");
return;
}
But i get an exception that Form values are read only.
What would be a way to send the client to another page, pre-filling form data?
Answer
i used the Session state to store values. It's important to note that by default a Handler doesn't have access to Session (the Session object will be null). You have to tell IIS to give you the Session object by adding the IRequiresSessionState marker interface to your handler class:
public class Handler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
...
if (theyWantToDoSomething)
{
//pre-fill form values
context.Session["thing1"] = "test";
context.Session["thing2"] = "test 2";
context.Session["thing3"] = GetTheTextForTheThing();
//tell the client to go there
context.Response.Redirect("~/SomeWebForm.aspx");
return; //not strictly needed, since Redirect ends processing
}
...
}
}
You can only populate your Response, the Request is input data and is indeed read-only.
If you are using ASP.NET, there are a variety of ways you could accomplish what you need:
The best way would probably be to pass the data you need to be pre-populated to SomeWebForm.aspx via the Session object, and on that pages Load method, populate your form. Keep in mind that when you do Response.Redirect, a 302 response is sent to the client with the URL the client should redirect to. The process is transparent to the user...but there is a full round trip involved.
Another alternative to populating the users Session would be to add GET parameters via a query string to the redirect to SomeWebForm.aspx.
If you need to transfer processing to the SomeWebForm.aspx page without round tripping, you could use Server.Transfer. This will transfer execution from the current page to the page you choose...however, this can cause some odd behavior on the client end because the URL does not update. As far as the user is concerned, it will still appear as though they are on the same page they started on.
A few ideas that might get you started:
Pass the values in the query string
Store the values in the session state or in a seperate cookie
Store the values in HttpContext.Items and use Server.Transfer instead of Response.Redirect
Another approach that hasn't been mentioned yet is using Server.Transfer makes it possible to use the Page.PreviousPage property, allowing access to the controls on the page that transferred control.
As jrista mentioned though, using Transfer doesn't update the URL shown to the user, which may or may not be an issue. For example, a user can't precisely bookmark a page they got transferred to since the URL will be that of the original transferring page.

Resources