ASP.NET MVC: Using GET and POST in the same method - c#

Here's the scenario: when a new user registers to our web site, we want to send an email to verify that the user owns the email address. In the email there's a link to a page that will do the verification, something like this:
http://www.mysite.com/account/verify/token
The verify method looks like this:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Verify(Nullable<Guid> id)
{
// tries to get the user based on the verification code
if (ValidId(id))
{
// if the id is correct, update user data in the model and redirect
return RedirectToAction("Index", "Dashboard");
}
else
{
// redirects the user to the verify view
return View("Verify");
}
}
The "Verify" view is simply a textbox with a button, so the user can enter the verification code manually (the user can get to this page from the site, and might prefer just to copy-paste the code). When the user clicks on the button, I want to do the same thing that my GET method does; so I ended up with something like this:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Verify(Nullable<Guid> id) { ... }
I have a couple of issues with this code (which works, but...):
Is it OK to have a GET and POST method? Or is there a better way to handle this scenario?
I'm modifying data in a GET method (if the id is correct, I update the user data to reflect that it's verified) and that's a big NO NO ... still, I want the user to just be able to click on the link and verify the token. Is there a better way to achieve this?
Thanks

I personally wouldn't bother with the AcceptVerbs attribute. (** See note below) You could then combine this into one action, which could respond as needed. (Showing some untested code below.) The reason I'm adding an answer instead of just a comment is that I wanted to recommend you add one more branch to your logic, to handle a failed code (i.e., to present an error message).
public ActionResult Verify(Nullable<Guid> id)
{
if (!id.HasValue)
{
// nothing was submitted
ViewData["message"] = "Please enter your ID and press Submit";
return View("Verify");
}
if (!ValidId(id))
{
// something was submitted, but wasn't valid
ViewData["message"] = "ID is invalid or incomplete. Pleaes check your speeling";
return View("Verify");
}
// must be valid
return RedirectToAction("Index", "Dashboard");
}
You then of course could display <%=ViewData["message"]%> in your Verify view. This is of course just a simple example.
** OK, here is my note RE: not bothering with the AcceptVerbs attribute:
In your scenario you could also just choose to make your form's method GET instead of POST. Because you're already "taking action" and modifying state on the handy link your users click on, I wouldn't see any difference. I'm just mentioning this to be thorough even though I'd personally opt for my previous recommendation.
Good luck!

I'm modifying data in a GET method ... and that's a big NO NO
I wouldn't say it's always a big no no. HTTP says that the GET method "SHOULD" be "safe", that is it SHOULD have no effect other than information retrieval. In this case, I think it's reasonable to stretch the definition of "safe" to mean that it doesn't have any harmful side effects, and indeed the only possible side effect your verification link can have is a desirable one.
The other property that the GET method is supposed to have is idempotence: if the user clicks the verification link multiple times, it's the same as if they clicked it just once. Hopefully you have this property, since a verification link is generated with a single-use code.

I have to say it is rare to find somebody so concerned with using the proper HTTP verb. I don't believe the original intention of the HTTP spec was to confine all data editing submissions to POST and all retrievals to GET. I think what you're doing is just fine.

if you are worried about it then what is wrong with this?
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Verify()
{
return View("Verify");
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Verify(Guid? id)
{
if (ValidId(id))
{
return RedirectToAction("Index", "Dashboard");
}
return View("Verify");
}

Related

Security issues with sending back sensitive info after failed form submission

I have a ASP.NET Core application where I have to collect a handful of information about an applicant. One of the fields is SSN.
I have a GET action in my controller that displays the fields of the form. It looks like this:
[HttpGet]
[Route("~/applicant/")]
public IActionResult Applicant(int id, Guid guid)
{
var model = CreateEmptyViewModel();
return View(model);
}
I then have a POST action in my controller that checks if the form submission is valid and moves on or reloads the form accordingly.
[HttpPost]
[Route("~/post-applicant")]
public IActionResult PostApplicant(MyViewModel model)
{
if (model == null) throw new ArgumentNullException(nameof(model));
if (ModelState.IsValid)
{
// code that moves on
}
else
{
TempData["Error"] = "The form is incomplete or corrections are needed.";
return View(nameof(Applicant), model); // reloads form with fields filled out
}
}
My view model looks like this:
public class MyViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string SSN { get; set; }
}
All properties in MyViewModel are required. If the user decides to supply SSN but not first name, the form submission will fail and the form will be reloaded.
Are there any security related ramifications for reloading a form with recently typed sensitive information? Is there a better way to do what I am doing?
Encrypting your requests and response via SSL is good first step, but it is not a solution in itself. HTTPS traffic can be intercepted and man-in-the-middle attacks are a real concern, particular on insecure networks like public wifi. Unless you can ensure that all your users are running latest and greatest platforms with all security patches applied and are always connected to secure networks directly or via a secure VPN, then you can't just brush off PII as fine because it's HTTPS. Even then, there's always zero-day exploits you can never account for.
Long and short, you should treat all sensitive PII as sacrosanct at all times and never transfer it over the line unless you have to. Initial collection is one such occasion, but that doesn't mean it should come back over the line on error. It's perfectly okay to make a user re-enter sensitive information again, and most users tend to understand why they have to. For example, if you make an error with a credit card payment form, your credit card number doesn't come back filled in already - that would be a severe violation of PCI.
So, in your controller action, you should do the following:
ModelState.Remove("SSN");
model.SSN = null;
return View(model);
That said, there's probably worse PII to potentially leak at this point. Thanks to Equifax, virtually everyone's SSN is already public. Still, it's always good to think about what data you're sending back and forth.
As long as you are using HTTPS, then there are no security implications.
The only way you could make it better is by doing the POST as an AJAX request in the background. Then you only need to return the error message if there is one. The only real benefit is a better user experience since they don't need to wait for a full page refresh. There's an example of that here: https://stackoverflow.com/a/6960586/1202807
But the way you're doing it is fine too.

How to verify user role before executing action?

I'm working on a project in which some users can be in the role AdminReader. Those users can see everything, but will not be able to save/edit any data.
I know I can do it this way:
public JsonResult ChangeStatus(int? id)
{
// AdminReader validation
if (base.User.isAdminReader)
{
return Json(new
{
Message = "You don't have privileges to alter data.",
Success = false,
}, JsonRequestBehavior.AllowGet);
}
// Function code
But I don't want to insert the above code inside all project functions.
I thought I could decorate my methods like we use [HttpGet]. I've also read this SO post.
Then I dropped the idea.
But then I found about Exception Handler Attribute and a logging action filter.
Is it possible to somehow combine the public void OnActionExecuting(ActionExecutingContext filterContext) with my AdminReader validation?
I don't know if it is the right way to go about my problem. Also, I'm not sure it could work really. What's the best practice in this situation?
Any suggestion is welcome, thanks in advance.
There are many ways to do this.
Yes, it's true that attributes are just metadata. However, the MVC framework has code in it that recognizes certain metadata and performs actions on it. Examples include the two attributes you mentioned (ActionFilters and ExceptionFilters), there's also AuthorizationFilters, which may be what you actually want.
AuthorizationFilters run before ActionFilters, near the start of the MVC pipeline, which allows them to block access before the page actually renders. But, if you don't need that, you can just use this point to do specific things before the page renders.
However, having said that, you are still going to need to have code on each page that controls what the user can and can't do based on their role. There is no magic way around that. Whenever you want to control what a user can do on a page based on access, you need code that does that in each section where control is required.
It's not clear from your example what you are trying to do, since the return value from a page is typically the HTML to render, but it looks like you want to return some kind of status message. I don't see how that can be replicated to all pages, since the pages themselves need to render.
I'm not entirely sure I understood your question, so sorry if this is off: but if you wanted to perform your AdminReader logic, you could write your own custom attribute like below:
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult)
{
// Perform your unauthorized action here.
}
}
}
And then throw the attribute on any method where it applies (or you could throw it on the entire Controller class, if it applied to everything). Like so:
// The RoleSettings is a class of constants I defined that just contain strings
[AccessDeniedAuthorize(Roles = RoleSettings.AdminRole]
[HttpPost]
public ActionResult MyEditMethod()
{
// Perform actions if they are in the AdminRole
// If not authorized, it will do whatever you defined above in the
// AccessDeniedAuthorizeAttribute
}

ASP.Net - C# How to make a password protected view so data on page is protected until the password is correct

I have a page that when accessed has some sensitive information. I understand there are better ways to go about this like Active Directory, etc, but I'm aiming for the simplest way possible at the moment.
Is there a way to prompt the user for a string via textbox, whether it be with an alert or any other method, that will pause the DOM to be displayed and accessed via "view page source"? So that if entered incorrectly, the view is not returned?
Could someone please provide a code example?
The page I have currently is being accessed by this actionresult:
public ActionResult ViewRecords() {
return View();
}
Is there anyway to make is so that the code above relies on the correct string to be passed in like so:
public ActionResult ViewRecords() {
alert(//some sort of text box//);
if (string == 'password')
return View;
else
return View("Index.cshtml");
}
I've done some research and found some people doing this through a webconfig definition, but am confused on the exact code I'd need to put in both my web.config, c# controller file, and .cshtml view to make this work.
[Http.Get]
public ActionResult SafePage() {
return View("PageThatPromptsForPassword");
}
inside "PageThatPromptsForPassword" view, write some JS to prompt the user for a password. Pass it along (using AJAX or a form) to a 2nd controller action to verify.
[Http.POST]
public ActionResult ConfirmPassword(string passWord) {
if (passWord == MY_SECRET_PASSWORD) {
return View("SecretInformationPage");
} else {
return View ("PageThatPromptsForPassword");
}
}
** Note this is, as you asked, very simple and may have potential security issues. But it's a starting point.

c# pass a string from an action to another

Imagine a page where the user can update, delete, edit or even vote the input. Since each action will be handled by a different ActionResult, the url of the page will have to change in case there are errors. My question is simple: Say your page is "http://localhost/Input/" and when you there are some errors, I want to redirect to "http://localhost/Input/Error" and display an error message. I don't want to use sessions for this therefore please don't show me this link. Basically, want I want to do this is something similar:
public ActionResult Create(FormCollection form) {
try {
// some code here
return RedirectToAction("Index");
}
catch () {
string errorMessage = "You have done something bad!";
// and I want to pass this string to my Error Action.
return RedirectToAction("Error");
}
}
The Solution I've Used
I know that I said I don't want to use TempData, but apparantly that is the best option and I've used that for my issue. As for the answer I've chosen DigBySwift's answer because that's the most logical thing you can do if you don't want to use Session for this type of operation.
As you say, if you don't want to use sessions then TempData is not an option. Ideally, you should try not to pass the error message to the next page. Instead store the error message(s) in a resource file.
You could pass an id/enum to you next page and then retrieve the error based on the passed parameter:
return RedirectToAction("Error", new { ErrorId = 4 });
public ActionResult Error(int errorId){ ... }
Edit: An alternative (since a db write-read would be expensive considering) would be to write the message to a cookie and then retrieve it after the redirect, and then delete the cookie. This is not great and I personally would not use this. But if you need to customise the message, it is an option.
Is there an option of saving the error message in database from the sender page and getting it back on the other page ?
I know its similar to the one posted above but it will be more dynamic as you dont need to put the message in resource file and you can save the error message in db and get that on other page
I'd like to suggest an alternative way. I use AJAX to do these types of operations, thus instead of redirecting user to another view, I can return a view, or mostly a partial view from my controller. Thus I can use that string message right inside my view using ViewBag.
public ActionResult Create(FormCollection form) {
try {
// some code here
return View('CreationSuccess');
}
catch () {
ViewBag.errorMessage = "You have done something bad!";
// and I want to pass this string to my Error Action.
return View('CrudError');
}
}

What is a good method for preventing a user from submitting a form twice?

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.

Categories

Resources