Is anti forgery token added automatically, even without explicit [AutoValidateAntiforgeryToken]? - asp.net-core

Context
I've noticed that after creating a new ASP.NET Core Razor page application in VS 2019 from its out of the box template, even the purest html form with the purest model class renders output with <input name="__RequestVerificationToken" type="hidden" value="...">
Question
Am I missing something and there is somewhere an explicit attribute/statement which instructs ASP.NET Core to add anti forgery or now this is the default? (which makes using [AutoValidateAntiforgeryToken] obsolete)
...or...
It is just the <input name="__RequestVerificationToken" type="hidden" value="..."> which is rendered always unconditionally and with the [AutoValidateAntiforgeryToken]I can turn on the server side validation against it? This case how can I smoke test if validation is in effect or not?
Sample Code
#page
#model TestFormModel
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<form method="post">
<input type="text" name="myinput"/>
<input type="submit" value="Submit" />
</form>
</div>
//[AutoValidateAntiforgeryToken]
public class TestFormModel : PageModel
{
private readonly ILogger<TestFormModel> _logger;
public TestFormModel(ILogger<TestFormModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
public void OnPost()
{
}
}

Previously in .NET Framework versions of ASP.NET you did have to opt-in to anti-forgery token usually with an attribute.
[ValidateAntiForgeryToken]
public ActionResult Save(Product product)
{
db.Product.Add(product);
Return View();
}
In ASPNET Core this automagically included in the Form Tag Helper. So any time your CSHTML includes a FORM element, the hidden field is included for you by the ASPNET Core runtime.
The basis for including this by default is the mantra of "Convention over configuration". By convention, 80+% of developers would opt to protect their application against CSRF attacks. If you wish to go against the convention, you can find the option to opt out in the conventions helper in the ConfigureServices portion of your Startup class.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions
.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
});
}
This blog post goes in further detail specific to Razor Pages, options and usage scenarios.
Update - Response to comment
If you read the a code, you may notice that there is no taghelper. –
g.pickardou
There is indeed a tag helper. In a new Razor Pages project template you can find the tag helpers are included in the _ViewImports.cshtml file here:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
We can validate that your <form /> element, as written in the OP is invoking an ASP.NET tag helper as follows:
<form method="post">
<input type="text" name="myinput"/>
<input type="submit" value="Submit" />
</form>
If we inspect the page source on this, you will see the result
<form method="post">
<input type="text" name="myinput" />
<input type="submit" value="Submit" />
<input name="__RequestVerificationToken" type="hidden" value="{{token}}" />
</form>
Now, if we use the syntax to opt out of individual tag helpers
<!form method="post">
<input type="text" name="myinput" />
<input type="submit" value="Submit" />
</!form>
And again inspect the page source we can clearly see we have explicitly opted out of this tag helper.
<form method="post">
<input type="text" name="myinput" />
<input type="submit" value="Submit" />
</form>

For .Net 3.1 the form helper does add the validation token to forms when you use it like <form asp-action="...
With asp.net core 3.1 with a form that does not use asp-action and or asp-controller
like:
<form asp-action="Index" asp-controller="Home" method="post">
and uses this:
<form action="Index" method="post">
To include this: (in the form {before the closing form: })
<input name="__RequestVerificationToken" type="hidden" value="..." />
I just add this to the form:
asp-antiforgery="true"
like:
<form action="Index" method="post" asp-antiforgery="true">
Always works for me
This does not work for me:
<input name="__RequestVerificationToken" type="hidden" value="{{token}}" />
I just get that exact thing which doesn't have the token.
Of course you then need the decorator before your method like:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(...
Hope that helps someone searching for how to include the RequestVerificationToken or ValidateAntiForgeryToken

For the later Core versions (6,7 and maybe earlier), here's what the documentation states regarding when/if a token will be generated automatically:
The automatic generation of antiforgery tokens for HTML form elements happens when the <form> tag contains the method="post" attribute and either of the following are true:
The action attribute is empty (action="").
The action attribute isn't supplied (<form method="post">).
That mean that if to set a form's action attribute to a custom value, the "Antiforgery" element won't be injected automatically.
Here are a few ways one can do in those cases to have one injected:
Add the tag helper asp-antiforgery="true" to the form element
Add #Html.AntiForgeryToken() within the form element
Add #inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf to the view and
<input id="__RequestVerificationToken" type="hidden" value="#Xsrf.GetAndStoreTokens(Context).RequestToken" /> within the form element
Use the submit element's formaction attribute instead of the form's.

Related

onget handler razor pages

I have a button on a GET Form on asp.net core razor pages
<form method="get" asp-page="Index">
<button type="submit" asp-page="Index" asp-page-handler="Something" class="btn btn-primary"></button>
</form>
and the code behind
public IActionResult OnGetSomething(){
//... some code
return Page();
}
My problem is the onget handler code is never executed
If the form is POST the onpost handler will work fine but if it is GET it doesn’t work
So what am I missing here? and how to make the onget handler work?
When you submit a form using GET, the browser trashes query string values in the form action and replaces them with a new query string built from the form fields. You can hit your handler by adding an appropriate hidden field to the form:
<form method="get" asp-page="Index">
<input type="hidden" name="handler" value="Something" />
<button type="submit" class="btn btn-primary">Get</button>
</form>
you can't go to get method because of parameters miss matching.
Suppose:
In controller
[HttpGet]
public IActionResult Create()
{
return View();
}
//Post Method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(string Name)
{
}
In View:
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>]
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
Then it go post method cause it has 1 parameters (Name). if we not net any input or parameters are not same then it hit different method.
It the concept of method overloading
In details of method overloading https://www.geeksforgeeks.org/c-sharp-method-overloading/

razor pages form post handling

I have a simple html form
<form method="post" asp-page="testbank" enctype="application/x-www-form-urlencoded">
<input type="text" name="d" />
<button type="submit">Submit</button>
</form>
and I want to post to another razor page called testbank. In the testbank razor page I have
public void OnPost()
{
var a = Request.Form["d"];
}
but the post action is not triggered even when I add a breakpoint. I can't figure out why is that happening. I want to post data to another razor page.

Scaffolded Identity Razor Pages Forms don't submit unless there is a route parameter

This happens on all the Scaffolded Identity Razor Pages that do not include a route parameter in the Form definition, but let's take the ForgotPassword one as an example:
Here is the form as defined on the scaffolded page:
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
When I click the submit button, nothing happens. The browser dev tools confirm nothing happens. The page does not submit to the server. (and yes, I have entered a valid email address; if I don't, then the jquery validation correctly displays the appropriate error messages.)
But I've found that if I change the form definition to include a route parameter, as follows:
<form asp-route-unusedArg="SomeValue" method="post">
then the submit button works fine, and the form is submitted to the server. (Even though the OnPostAsync method defined by the server code does not expect a parameter at all; it is defined as public async Task<IActionResult> OnPostAsync()). The way I figured this out was to compare the form definition with that of the Login form, which was working fine, and had a route parameter for returnUrl.
What is going on here? Why is the form not submitting unless I add a route parameter?

Checkbox Click Form Submit in Razor Pages AspNetCore 2.2

I am trying to (non-Ajax) get a checkbox to resubmit form in Razor Pages and reload the page / and catch the result in my OnPost method.
I have in my index.cshtml
#page "{id:int?}"
#model IndexModel
<div class="text-center">
<form action="post" name="form1">
<strong>Filter:</strong> Hide Single Runs <input onclick="document.form1.submit()" asp-for="#Model.HideSingle" />
<hr />
And in my PageModel
public class IndexModel : PageModel
{
public bool HideSingle { get; set; } = true;
public async Task OnPost(int? id, bool hideSingle)
{
for say a starting page URL:
http://localhost:5000/TestRuns/1
The form submits on the checkbox click, but it ends up with a Url:
http://localhost:5000/TestRuns/post?HideSingle=false
Which obviously fails to resolve as I am expecting a route of http://localhost:5000/TestRuns/1.
For Asp.net Core form, the default method is Get which means for your current code, it send request with Get instead of post. You could specify the method with post like
<div class="text-center">
<form method="post" name="form1">
<strong>Filter:</strong> Hide Single Runs <input onclick="document.form1.submit()" asp-for="#Model.HideSingle" />
<hr />
</form>
</div>
For another way, you could explictly set the handler like
<div class="text-center">
<form asp-page-handler="post" name="form2">
<strong>Filter:</strong> Hide Single Runs <input onclick="document.form2.submit()" asp-for="#Model.HideSingle" />
<hr />
</form>
</div>
<input onclick="document.form1.submit()" />
is using JavaScript to submit the form. Do you want the page to post back, since you're not using Ajax (from your question)?
You want the call from your page to end up being http://localhost:5000/TestRuns/post?id=1&HideSingle=false. Is id required for your post to work (it's unclear with OnPost(int? id..)?
I order to get both values, you'll need to have hidden form values or multiple <input asp-for="Email" class="form-control" />. You need 1 for hideSingle and on for the id.

asp-for tag adds required field validation on checkbox in asp.net core

I have asp.net core application and im trying to add simple checkbox without any validation. Checkbox is bound to boolean property on model. Below is the code
Model
public class MyModel
{
public bool IsEmployee { get; set; }
}
cshtml
<form>
<div>
<label asp-for="IsEmployee">Is Employee</label>
<input type="checkbox" asp-for="IsEmployee"/>
</div>
<button id="btnSave" class="btn btn-primary" type="button">Save</button>
</form>
<script src="~/js/test.js"></script>
javascript
$(function () {
var kendoValidator = $('form').kendoValidator().data("kendoValidator");
$('#btnSave').click(function () {
if (kendoValidator.validate()) {
alert('true');
}
else {
alert('false');
}
})
})
I am using asp-for tag helper on input element. Note that IsEmployee property DOES NOT have [Required] attribute. But because of asp-for tag helper the rendered html has data-val-required and data-val attributes on input element. It also adds one more hiddden input element with same name.
Below is rendered html.
(also note that i think it only happens when input type is checkbox. for textboxes its working fine)
<form novalidate="novalidate" data-role="validator">
<div>
<label for="IsEmployee">Is Employee</label>
<input name="IsEmployee" id="IsEmployee" type="checkbox" value="true" data-val-required="The IsEmployee field is required." data-val="true">
</div>
<button class="btn btn-primary" id="btnSave" type="button">Save</button>
<input name="IsEmployee" type="hidden" value="false">
</form>
I am using kendovalidator as well which adds data-role="validator" on form element.
Issues
There are 2 issues here
1> As soon as i click on check the box error message appears as The IsEmployee field is required.
2>kendoValidator.validate() method always returns false regardless of checkbox is selected or not.
Demo here JSFiddle
Update 2
We cannot bind nullable bool to checkbox. I am using asp.net core. I am not sure what the equivalent syntax in asp.net core for the suggestion here which is valid for classic asp.net
Add data-validate="false" to the checkbox input. The kendoValidator will ignore all inputs with that attribute set to false.
<input type="checkbox" asp-for="IsEmployee" data-validate="false" />
If you don't wan't the default generated html you have 2 choices.
Don't use it ! You are not forced to use the tag helpers, they are there for when you do need other html attributes generated. In this case just use < input name="IsEmployee" ...>
Change the way asp-for behaves for your checkbox. You can do this be either creating your own IHtmlGenerater or by extending the DefaultHtmlGenerator and overriding GenerateCheckBox and possibly GenerateInput and then registering it with something like services.TryAddSingleton();
Hope this helpes you.