I've been trying to implement a simple server with cookie-based authentication using Servant. I found an example here.
I created my API:
type API
= Auth '[Cookie] User :>
"login" :> ReqBody '[JSON] User :> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
, Header "Set-Cookie" SetCookie ] NoContent)
:<|> "logout" :> Get '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
, Header "Set-Cookie" SetCookie ] NoContent)
Here's an implementation for the endpoints:
checkCreds :: CookieSettings
-> JWTSettings
-> Credentials -- my type storing user's login and pass
-> Handler (Headers '[Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] NoContent)
checkCreds cookieSettings jwtSettings Credentials { credentialsUserName = userName, credentialsPassword = pass} = do
authRes <- checkIfUserExists userName pass
case authRes of
Just (name, key) -> do
mApplyCookies <- liftIO $ acceptLogin cookieSettings jwtSettings (Session key name)
return $
case mApplyCookies of
Nothing -> clearSession cookieSettings NoContent
Just applyCookies -> applyCookies NoContent
Nothing ->
throwError err401
getLogout :: CookieSettings
-> Handler (Headers '[ Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie ] NoContent)
getLogout cookieSettings = return $ clearSession cookieSettings NoContent
The cookieSettings
I use are here:
cookieSettings :: CookieSettings
cookieSettings = defaultCookieSettings {
cookieIsSecure = NotSecure,
cookieSameSite = SameSiteStrict,
sessionCookieName = "MyCookie",
cookieXsrfSetting = Just def {xsrfExcludeGet = True}
}
I use JavaScript fetch
to poke the login
endpoint:
let opts: RequestInit = {
method: "POST",
headers: new Headers({ "Content-Type": "application/json" }),
credentials: "include",
body: json,
};
fetch("http://localhost:8081/login", opts)
.then((value) => {
// Do something
}
);
This works fine and I noticed the cookies are included in the response and I can find them in Storage
-> Cookies
in my Firefox.
Then I use a similar method to poke the logout
endpoint:
const sendLogOut = async () => {
let resp = await fetch(Urls.logout.href, { method: "GET" });
console.log(resp.status);
};
it prints 200
in my console and I can see the cookies are included in the response:
However, nothing else happens. It seems that the response gets discarded and the cookies I had received from login
are still valid.
1.) How shall I implement the logout
feature properly.
2.) As I'm relatively new to web development, where can I find useful information about HTTP protocol? By "useful" I meant "something that shows examples instead of raw definitions".
I've got the answer.
In my case the problem was caused by the fact that my servant app and my JS client were technically two separate applications, hosted on two different ports (8081 and 8833 respectively).
When I set up nginx and configured routing under a single domain, everything works as expected.