Csrf cookies in Dotnet with axios
CSRF validation in .Net Core is a breeze when using Razor syntax or even jQuery, but how should you approach this for a front-end project that uses axios? The official documentation gets you almost all of the way, but axios has a great feature to make this as painless as possible with the axios.defaults.xsrfHeaderName
and axios.defaults.xsrfCookieName
with minimal changes in .Net Core land.
Overview:
- Scaffold new MVC project
- Follow official docs + extend
- Add an Antiforgery enabled endpoint to test
- Add axios
- make axios call the endpoint with AntiforgeryValidation
Scaffold new Mvc project
first scaffold a new MVC project in dotnet core:
dotnet new mvc --name Csrf.Sample
Follow official docs + extend
First configure Starup.cs
while referencing the Starup.ConfigureServices docs and the extra annotations in comments below:
// Startup.cs
// ...
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAntiforgery(options => {
options.HeaderName = "X-XSRF-TOKEN";
});
services.Configure<CookiePolicyOptions>(options =>
{
// All unessential cookies (including anti-csrf) will not be allowed until consent is granted by default.
options.CheckConsentNeeded = context => true;
});
// ...
}
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
// ...
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (
// You can modify what gets a csrf token here
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
// IF using CookiePolicyOptions IsEssential needs to be true
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false, IsEssential = true });
}
return next(context);
});
// ...
}
Some important points:
- if
options.HeaderName
is null, it will only work for form data (ref). options.Cookie.Name
must not match thecontext.Response.Cookies.Append
token name.- if using
CookiePolicyOptions
, thecontext.Response.Cookies.Append
CookieOptions needsIsEssential
to add to the client before consent.
Start the application (dotnet run
), go to the homepage (https://localhost:5001
) and open up the developer tools. Confirm that the client code can read the XSRf-COOKIE
in the network tab as well as document.cookie
in the console section like the picture below:
Add an Antiforgery enabled endpoint to test
In Controllers/HomeController.cs
add the following route:
[ValidateAntiforgeryToken]
public IActionResult WelcomeMessage(){
return Ok("Great!");
}
Next, to test this with axios.
Add Axios
First the Axios libary is needed. For brevity and ease-of-use, use the CDN version In Views/Shared/_Layout.cshtml
add the following parts below the annotation <!-- Added here
:
// _Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... omitted for brevity -->
</head>
<body>
<!-- ... omitted for brevity -->
<footer class="border-top footer text-muted">
<div class="container">
© 2019 - Csrf.Sample - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<!-- Added here for development -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-xrRywqdh3PHs8keKZN+8zzc5TX0GRTLCcmivcbNJWm2rs5C8PRhcEn3czEjhAO9o">
</script>
<!-- added here for production -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
After the axios library is added, axios can now call the endpoint from Home.cshtml
. In Views/Home/Index.cshtml
add the following snippet to the end:
@section scripts {
<script>
document.addEventListener("DOMContentLoaded", function() {
window.axios.get('/home/WelcomeMessage').then((response) => {
alert(response.data);
}).catch((err) => {
console.log(err);
})
});
</script>
}
Where’s the magic?
Axios has a default setting for CSRF cookies halfway down the Request Config section that will explains it.
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
The defaults in axios are what we set in the Startup.cs
. Whenever axios makes a request, it will check the document.cookie
for a value called XSRF-TOKEN
and add it as a header X-XSRF-TOKEN
.
Some considerations:
This will not provide a new token for every request. If there is interest, I may look into it. Additionally, this integration works just as well for Vue/Nuxt/React apps that use axios as it is the default behavior.