I’m working with an ASP.NET 6 app, generated with ASP.NET Core with React.js
Visual Studio 2022 template. I’ve used Individual Accounts
as Authentication Type
when creating the project, so all Identity stuff has been nicely generated.
Now I have nice Razor views scaffolded by ASP.NET’s Identity. However, I’d like to build my whole UI as React SPA application, using react-router
. It means that I don’t want to use Razor views, but still use ASP.NET’s Identity backend.
Firstly, I wanted to implement a React form to submit changing the user password. Razor view generated for that is Identity/Pages/Account/ManageChangePassword.cshtml
. It looks like that:
As soon as I submit this Razor form, the request looks as follows:
with the following payload:
So now, I basically rebuilt this form in React:
import React, { useState } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
export const ChangePassword = () => {
const [currentPassword, setCurrentPassword] = useState<string>("");
const [newPassword, setNewPassword] = useState<string>("");
const [newPasswordConfirm, setNewPasswordConfirm] = useState<string>("");
const onChangePasswordFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append("Input.OldPassword", currentPassword);
formData.append("Input.NewPassword", newPassword);
formData.append("Input.ConfirmPassword", newPasswordConfirm);
fetch("Identity/Account/Manage/ChangePassword", {
method: "POST",
body: formData,
});
};
return (
<Form onSubmit={onChangePasswordFormSubmit}>
<Form.Group className="mb-3" controlId="currentPassword">
<Form.Label>Current password</Form.Label>
<Form.Control
type="password"
placeholder="Current password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="newPassword">
<Form.Label>New password</Form.Label>
<Form.Control
type="password"
placeholder="New password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="newPasswordConfirm">
<Form.Label>Confirm new password</Form.Label>
<Form.Control
type="password"
placeholder="Confirm new password"
value={newPasswordConfirm}
onChange={(e) => setNewPasswordConfirm(e.target.value)}
/>
</Form.Group>
<Button variant="primary" type="submit">
Change password
</Button>
</Form>
);
};
However, when submitting this form, I’m getting a HTTP 400 error:
the payload looks good at the first sight:
but I noticed that I’m missing the __RequestVerificationToken
in this payload.
I guess it’s coming from the fact that Identity controllers (to which I have no access) must be using [ValidateAntiForgeryToken]
attribute.
If I change my form’s submit code to add this payload parameter manually:
const formData = new FormData();
formData.append("Input.OldPassword", currentPassword);
formData.append("Input.NewPassword", newPassword);
formData.append("Input.ConfirmPassword", newPasswordConfirm);
formData.append(
"__RequestVerificationToken",
"CfDJ8KEnNhgi1apJuVaPQ0BdQGnccmtpiQ91u-6lFRvjaSQxZhM6tj8LATJqWAeKFIW5ctwRTdtQruvxLbhq2EVR3P1pATIyeu3FWSPc-ZJcpR_sKHH9eLODiqFPXYtdgktScsOFkbnnn5hixMvMDADizSGUBRlSogENWDucpMgVUr3nVMlGwnKAQDH7Ck4cZjGQiQ"
);
fetch("Identity/Account/Manage/ChangePassword", {
method: "POST",
body: formData,
});
};
It works fine and the request arrives correctly.
My question is: where to get __RequestVerificationToken
from? How can I send it to the ASP.NET’s Identity controller from a purely React form?
I noticed that when submitting my React form, this value is visible in cookies:
so the React form/browser must somehow know this value? Where does it come from?
Maybe my approach is somehow wrong here? Thanks for advising 🙂
2
Answers
The AntiForgeryToken is generated by
And is validated by
I’d send
AntiForgery.GetHtml()
to the client, and then validate it on the server.Maybe you can even create an ajax endpoint that returns new tokens to the Client.
or more from here on Microsoft Ref, theres stuff on doing it for SPA apps all well further below