withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
46.81k stars 2.49k forks source link

Cookies not accessible when I fetch in frontmatter #10592

Closed haocloo closed 7 months ago

haocloo commented 7 months ago

Astro Info

Astro                    v4.4.15
Node                     v18.18.0
System                   Windows (x64)
Package Manager          pnpm
Output                   server
Adapter                  @astrojs/cloudflare
Integrations             @astrojs/tailwind
                         @qwikdev/astro
                         astro-compressor
                         @astrojs/react

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

I have enabled Astro SSR. This is my astro file

---
import Layout from "@layouts/Layout.astro";
import { generateCSRFToken } from "@modules/common";

const csrfToken = await generateCSRFToken();

const res = await fetch(import.meta.env.PUBLIC_DOMAIN + "/api/data", {
  headers: {
    "X-CSRF-Token": csrfToken,
  },
});
const result = await res.json();
---

<Layout title="Welcome to Astro.">
  <section>
result : {result}
  </section>
</Layout>

The generateCSRFToken function will send another fetch request which looks like this

import type { APIRoute } from "astro";
import CryptoJS from "crypto-js";
import { setCookie } from "@modules/common";

export const GET: APIRoute = async ({ cookies, request }) => {
  const csrfToken = CryptoJS.lib.WordArray.random(36).toString(
    CryptoJS.enc.Base64
  );

  setCookie(cookies, "X-CSRF-Token", csrfToken, false, "/", 1);

  return new Response(JSON.stringify({ csrfToken }), {
    status: 200,
  });
};

The issue is that if I try to access the cookie value by using cookies.get in the GET request, I got the value but if I do the cookies.get in the front matter or the middleware.ts, I'm not able to get the value. So, my temporary resort is to move the fetch request to the script tag which solves the issue of not being able to access the cookies values after sending the GET request.

What's the expected result?

After sending a GET request in the frontmatter which sets the cookie value, I want to be able to access the value of the cookie being set previously in the frontmatter.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-9qeh1c?file=src%2Fpages%2Findex.astro

Participation

ematipico commented 7 months ago

Thank you @haocloo , could you please create a reproduction that is actually usable? @modules/common is not even a dependency, so it won't even work if we tried to deploy it to cloudflare if we wanted to.

github-actions[bot] commented 7 months ago

Hello @haocloo. Please provide a minimal reproduction using a GitHub repository or StackBlitz. Issues marked with needs repro will be closed if they have no activity within 3 days.

ematipico commented 7 months ago

Why don't you use Astro.cookies? That's usually the solution that we ask users to use: https://docs.astro.build/en/reference/api-reference/#astrocookies

haocloo commented 7 months ago

hi @ematipico , I have modified the code in the stackblitz. The modules folder contains the reusable functions I would like to use. I apologize for any confusion. What I meant to convey is that I’m unable to execute the same code within the frontmatter. As you’ve noticed, the code in the script tag is identical to the (commented) code in the frontmatter. I could use the Astro.cookies.set("X-CSRF-Token",csrfToken) after this line const csrfToken = await generateCSRFToken(); in the frontmatter but when I try to access it via cookies.get('X-CSRF-Token').value in my /api/data, the value is undefined.

---
import Layout from "../layouts/Layout.astro";
// import { generateCSRFToken } from "@modules/common";

// const csrfToken = await generateCSRFToken();

// const res = await fetch("http://localhost:4321/api/data", {
//   headers: {
//     "X-CSRF-Token": csrfToken,
//   },
// });
// const result = await res.json();
---

<Layout title="Welcome to Astro.">
  <section>
    Result
  </section>
</Layout>

<script>
// my temporary solution
import { generateCSRFToken } from "@modules/common";

const csrfToken = await generateCSRFToken();

const res = await fetch("http://localhost:4321/api/data", {
  headers: {
    "X-CSRF-Token": csrfToken,
  },
});
const result = await res.json();
alert(result.data)
</script>
lilnasy commented 7 months ago

@haocloo Frontmatter runs on the server. HTTP requests made by the server do not include cookies. You also wouldn't want a server sending requests to itself as that could lead to loops.

Is this what you're looking to do? Call endpoints from the server

haocloo commented 7 months ago

thanks @lilnasy . I forgot that I could just run the server functions directly in the frontmatter instead of running them in the api. In the link you shared Call endpoints from the server, I noticed the use of GET function, I wonder if I were to reuse my api endpoints, how do I pass the csrfToken into the header? This is my current code:

---
import Layout from "@layouts/Layout.astro";
import { GET } from "../api/generateCSRFToken";
import { GET as GET2 } from "../api/data"

const result1 = await GET(Astro);
const { csrfToken } = await result1.json();

const res2 = await GET2(Astro);

// const res = await fetch(import.meta.env.PUBLIC_DOMAIN + "/api/data", {
//   headers: {
//     "X-CSRF-Token": csrfToken,
//   },
// });
const result = await res2.json();
console.log(result)
---
lilnasy commented 7 months ago

I would start with Astro.request.headers.set.

haocloo commented 7 months ago

Thanks @lilnasy again for helping me achieve what I wanted. I'm currently implementing it this way, I'm open to anymore suggestions or improvements.

---
import Layout from "@layouts/Layout.astro";
import { GET } from "../api/generateCSRFToken";
import { GET as GET2 } from "../api/data"

const result1 = await GET(Astro);
const { csrfToken } = await result1.json();

Astro.request.headers.set("X-CSRF-Token", csrfToken);
const res2 = await GET2(Astro);

const result = await res2.json();
console.log(result)
---
lilnasy commented 7 months ago

Looks great!