Issue
I have a SpringBoot Backend with Spring Security Enabled in the default configuration (nothing changed there).
I have following Rest Controller and Post Mapping:
@RestController
public class MyController {
@PostMapping(value = "/sm/resrc/pth")
public Integer postSomething(@RequestParam String someValue,@RequestParam String userId){
System.out.println(String.format("SomeValue: %s from userid %s",someValue,userId));
return 0;
}
}
- Creating a Post request from the following form works fine:
<form method="post" th:action="@{/sm/resrc/pth}">
<input type="text" name="someValue">
<input type="text" name="userId">
<input type="hidden" name="_csrf" value="${_csrf.token}" />
<div><input type="submit" value="Send" /></div>
</form>
It does even work without the hidden cors value.
- However I need to create a post request from JavaScript, which doesn't work. The SpringBoot application is running at localhost:8080. I thought, the credentials parameter 'include' is used for including the required authentication headers, which the user already entered successfully to open the given page. Is this correct? I also changed the value of 'mode'. I tried 'cors', 'same-origin' and 'no-cors'. It just does't work. I event don't understand why cors is a problem anyway as I am requesting a resource from the same origin. It didn't even work after adding the Authorization header manually to the request without using the credentials parameter.As you can see in the image I always get the 403 status. What is wrong with my request? What am I missing?
let data = {
"some":"abc",
"values":this.localDescription,
"userId":userId
};
fetch("sm/resrc/pth",
{
method: 'POST',
credentials: 'include',
mode: 'cors',
body: data
}
).then(response => console.log(response));
Solution
CORS is a security feature; So you probably don't want to disable or bypass it. I struggled a bit to find out, how to include the headers correctly but finally found the solution. That's how I solved it:
In the first step I added the expected CSRF-Header-name and the token itself to the metadata of the generated html file. In spring using the templating framework thyemleaf it looks like following:
<html lang="en">
<head>
<meta name="_csrf_header_name" th:content="${_csrf.headerName}"/>
<meta name="_csrf_token" th:content="${_csrf.token}"/>
</head>
.
.
.
</html>
Which results in something like that:
<meta name="_csrf_header_name" content="X-CSRF-TOKEN">
<meta name="_csrf_token" content="14d98c88-8643-1234-5678-39473aa7890e">
When the fetch request is created, the values of the header name and the token itself are read from the meta tags. That way the csrf header looks exactly the way, the server expects it to be. That was the main reason, why I struggled with the request. I didn't know, that I was naming the Header name of the token wrong. The expected Header/Token Name may vary from server to server - i guess; and thus can be different depending on your Backend.
getCsrfToken(){
return document.querySelector('meta[name=_csrf_token]').content;
}
getCsrfTokenName(){
return document.querySelector('meta[name=_csrf_header_name]').content;
}
let csrfToken = this.getCsrfToken();
let csrfTokenName = this.getCsrfTokenName();
fetch('sm/resrc/pth', {
method:'post',
headers: new Headers([[csrfTokenName, csrfToken]])
}).then(res =>{console.log(res);});}
Answered By - Kerem