I've found a lot of CORS-related questions, but none of them solved my problem. As I'm quite new to web-dev, I'd appreciate any help with getting my project infrastructure up and running.
My application is made of two parts:
Server part
- A server made with Haskell, Servant. The most important endpoint I've been trying to connect to is:
type Unprotected =
"login"
:> ReqBody '[JSON] Credentials
:> Verb 'POST 204 '[JSON] (Headers '[Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] NoContent)
It's based on the project described here: https://github.com/haskell-servant/servant-auth
I've already found a SO question about CORS: CORS header ‘Access-Control-Allow-Origin’ missing in servant, but that didn't help.
During development I launch the server on port 8081.
UI Part
- I have also a UI project, that's hosted independently on port 8833. It uses WebPack. I've also found some SO posts regarding CORS, advising to use:
devServer: {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
}
That didn't help either.
Problem description:
When I make a "fetch" call in the UI project, trying to communicate with the Servant endpoint I get:
Access to fetch at 'http://localhost:8081/login' from origin 'http://localhost:8833'
has been blocked by CORS policy: Response to preflight request doesn't pass access control
check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an
opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource
with CORS disabled.
The way I use fetch:
let data = {
credentialsUserName: this.state.login,
credentialsPassword: this.state.password
};
let opts : RequestInit = {
method: "POST",
headers: new Headers({ "Content-Type": "application/json" }),
body: JSON.stringify(data)
};
fetch("http://localhost:8081/login", opts).then((value) => {
console.log(value);
// do something later
});
Questions
What shall I do in order to make the fetch work fine with the servant endpoint. Also, is there anything wrong with my application architecture ? Maybe there's a better way to design it.
Last thing: The entire code of my application is quite long, so I decided to put only to most important parts here. Feel free to request more if needed.
Edit 1After sending the fetch request I can see two entries in "Network" tab in my browser.
The first one with the following headers:
content-type: application/json
Referer: http://localhost:8833/Login
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
and json data I included in its body. It's status is "(failed) net::ERR_FAILED".
The other's status is 400 and contains more headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:8081
Origin: http://localhost:8833
Referer: http://localhost:8833/Login
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Interestingly, In "General" tab I can see:
Request URL: http://localhost:8081/login
Request Method: OPTIONS
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:8081
Referrer Policy: no-referrer-when-downgrade
Request Method "OPTIONS" looks mysterious. Why is it there ?
As a quick guess I tried adding "OPTIONS" to CORS definition on Haskell side, but no luck this time either:
port :: Int
port = 8081
corsPolicy :: Middleware
corsPolicy = cors (const $ Just policy)
where
policy = simpleCorsResourcePolicy
{ corsMethods = [ "GET", "POST", "PUT", "OPTIONS" ]}
main :: IO ()
main = do
migrateDB
jwtConfig <- getJwtConfig
putStrLn $ "Serving endpoint " ++ (show port)
run port $ corsPolicy $ serveWithContext proxy (context cookieConfig jwtConfig) (appAPI cookieConfig jwtConfig)
EDIT 2
Thanks for the help - I've managed to configure Servant, here it is:
corsPolicy :: Middleware
corsPolicy = cors (const $ Just policy)
where
policy = simpleCorsResourcePolicy
{
corsMethods = [ "GET", "POST", "PUT", "OPTIONS" ],
corsOrigins = Just (["http://localhost:8833"], True),
corsRequestHeaders = [ "authorization", "content-type" ]
}
The remaining part of Main as in my previous listing. By default Haskell CORS doesn't accept any headers. Not even "Content-Type", which I intend to send.