I'm using React and Django for my web application. As far as I know, Django uses a double submit
pattern, where the CSRF token in the cookie and header/body are compared server side.
I use the following code to extract the CSRF token from document.cookie
as specified by Django docs:
export function getToken() {
let cookieValue = null;
const name = "csrftoken";
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(
cookie.substring(name.length + 1)
);
break;
}
}
}
return cookieValue;
}
I then use it like so:
axios
.post("/api/auth/login",
{ username: username, password: password, },
{ withCredentials: true, headers: { "X-CSRFToken": getToken(), } }
So my question is, I've heard that an attacker can't access a user's cookies and thus can't extract the CSRF token to place in the request header/body. But why can't the attacker execute a script running the getToken
function and place the result in a header like I did? (Say we ignore CORS for the sake of argument)
Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p id="p">Hello!</p>
<script>
function getToken() {
let cookieValue = null;
const name = "csrftoken";
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (
cookie.substring(0, name.length + 1) ===
name + "="
) {
cookieValue = decodeURIComponent(
cookie.substring(name.length + 1)
);
break;
}
}
}
return cookieValue;
}
document.getElementById("p").addEventListener("click", () => {
fetch("http://localhost:8000/api/auth/logout", {
method: "POST",
headers: {
"X-CSRFToken": getToken(),
}
})
});
</script>
</body>
</html>