Cookie not sent when requesting to API

Is there a way to determined why calling my API endpoint from a single page application fails but when calling it from POSTMAN it works?

Set cookie by logging in:

fetch("http://localhost:4000/api/session", {
      method: "POST",
      headers: {
        "content-type": "application/json",
      },
      body: JSON.stringify({
        email: "test3@test.com",
        password: "1234",
      }),
    })
      .then((res) => res.json())
      .then((res) => console.log(res));

Response headers received:

HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: http://localhost:3000
access-control-expose-headers: 
cache-control: max-age=0, private, must-revalidate
content-length: 87
content-type: application/json; charset=utf-8
date: Sun, 02 May 2021 11:25:13 GMT
server: Cowboy
vary: Origin
x-request-id: Fns8OscbqBybNl8AABXH
set-cookie: token=SFMyNTY.g2gDbQAAACQwOGEwOTFmMC1hNzNiLTRlMGMtYjIwOC1lN2UxMTlkNWRmOGFuBgCcRtMseQFiAAFRgA.81ytoRjjwFJDaNSW1RPRB27J3sf6Vx71nOXfuFks08g; path=/; expires=Sun, 09 May 2021 11:25:14 GMT; max-age=604800; HttpOnly

Function that sets the cookie in the response headers:

  def create(conn, %{"email" => email, "password" => password}) do
    with {:ok, %User{} = user} <- Accounts.authenticate_user(email, password),
         token <- Token.generate_token(user) do
      conn
      |> put_resp_cookie("token", token, http_only: true, max_age: 604_800)
      |> render("auth.json", user: user)
    end
  end

When accessing a protected endpoint, it fails since I have a plug that checks for “token” cookie and halt if the value is nil

The protected endpoint is just a test GET route

    fetch("http://localhost:4000/api/users/1", {
      credentials: "include",
    })
      .then((res) => res.json())
      .then((res) => console.log(res));

Response headers:

HTTP/1.1 401 Unauthorized
access-control-allow-credentials: true
access-control-allow-origin: http://localhost:3000
access-control-expose-headers: 
cache-control: max-age=0, private, must-revalidate
content-length: 56
content-type: application/json; charset=utf-8
date: Sun, 02 May 2021 11:25:19 GMT
server: Cowboy
vary: Origin
x-request-id: Fns8PB0iFMCQKBEAABBI

Upon inspecting Cookies tab in browser dev tools, it says No cookies for this request.

Can anyone guide me if there are any mistakes I do?

Thank you very much.

You are doing the request from Javascript to the API, not the browser, therefore you need to handle your self the cookies.

See Mozilla docs on cookies:

JavaScript access using Document.cookie

New cookies can be created via JavaScript using the Document.cookie property, and existing cookies can be accessed from JavaScript as well, if the HttpOnly flag is not set.

document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
// logs "yummy_cookie=choco; tasty_cookie=strawberry"

Cookies created via JavaScript cannot include the HttpOnly flag.

Please note the security issues in the Security section below. Cookies available to JavaScript can be stolen through XSS.

Bear in mind that API endpoints should use tokens, not cookies. Cookies are suitable for requests made by the browser, not by Javascript.

I would recommend you to use the Phoneix Tokens to protect your API endpoints:

https://hexdocs.pm/phoenix/Phoenix.Token.html

Example

When generating a unique token for use in an API or Channel it is advised to use a unique identifier for the user, typically the id from a database. For example:

iex> user_id = 1

iex> token = Phoenix.Token.sign(MyApp.Endpoint, "user auth", user_id)

iex> Phoenix.Token.verify(MyApp.Endpoint, "user auth", token, max_age: 86400)
{:ok, 1}

In that example we have a user’s id, we generate a token and verify it using the secret key base configured in the given endpoint. We guarantee the token will only be valid for one day by setting a max age (recommended).

Thank you for your response.

I solved the issue by setting this axios default config: axios.defaults.withCredentials = true;

Thank you so much Mate, It worked

The real answer is always in the credentials.