#middleware doesnt work with actions.

27 messages · Page 1 of 1 (latest)

buoyant sparrow
#

Hello im david and pretty new with astro, ive been developing a project with my backend made with rust and imconnecting everything with astro and solidjs. The thing is my back has a refresh token system and i need the middleware in the front to validate when needs to refresh and when needs to login. Im not a expert in how should i handle the cookies and tokens but read a lot to implement as much as i can. However when has to login to obtain both tokens(refresh and access), the middleware can not process, and sends an error, but when it has to refresh the token doesnt have the problem. The funny thing its when i remove all the logic in the middle, i can login but if i leave like that it not going to work and just only for refresh_token, the thing maybe its because the refresh_token is a secure cookie and cannot be accessed via server

true crane
#

Have you tried returning either context.redirect() or context.rewrite() instead of returning a Response?

buoyant sparrow
buoyant sparrow
# true crane Have you tried returning either `context.redirect()` or `context.rewrite()` inst...

still givin me the same:

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
    at JSON.parse (<anonymous>)
    at parse (parse.js?v=3f9f8615:17:24)
    at deserializeActionResult (shared.js?v=3f9f8615:198:11)
    at handleAction (__x00__astro:actions:104:9)
    at async HTMLFormElement.<anonymous> (login.astro:30:27)
import { PUBLIC_ROUTES } from "./consts";
// `context` and `next` are automatically typed
export const onRequest = defineMiddleware((context, next) => {
  const access = context.cookies.get("access_token");
  const refresh = context.cookies.get("refresh_token");

  if (PUBLIC_ROUTES.includes(context.url.pathname)) {
    return next();
  }

  if (!access && !refresh) {
    if (context.url.pathname === "/auth/login") {
      return next();
    }
    return context.redirect("/auth/login");
  }

  if (!access && refresh) {
    if (context.url.pathname === "/auth/refresh") {
      return next();
    }
    return context.redirect("/auth/refresh");
  }
  return next();
});
true crane
#

That's an odd error to get, I don't see you trying to parse json anywhere here unless im missing something

are the pages at auth/login finished and up and running?

buoyant sparrow
# true crane That's an odd error to get, I don't see you trying to parse json anywhere here u...
interface LoginResponse {
  status: "success" | "error";
  access_token: string;
  message?: string;
}

const auth = {
  login: defineAction({
    accept: "form",
    input: z.object({
      email: z.string().email(),
      password: z.string().min(1),
    }),
    handler: async (input: { email: string; password: string }, ctx) => {
      const { email, password } = input;
      try {
        const res = await fetch(`http://localhost:8000/api/auth/login`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ email, password }),
        });
        const data = (await res.json()) as LoginResponse;

        const cookieHeader = res.headers.get("Set-Cookie") as string;
        const token = cookieHeader.split("refresh_token=")[1].split(";")[0];

        ctx.cookies.set("refresh_token", token, {
          httpOnly: true,
          secure: true,
          sameSite: "none",
          expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
          path: "/",
        });

        if (!res.ok) {
          const errorMessage =
            (data as { message?: string }).message || "An error occurred";

          console.log(errorMessage);
        }
        return data;
      } catch (error) {
        console.log(error);
      }
    },
  }),
};

export default auth;

this is my action

true crane
#

Ah well that would be why then

#

You're trying to redirect to an action

buoyant sparrow
#
<Layout title="Login page">
<main>
    <h1 class="text-5xl text-orange-600">Login to your account</h1>
    <div id="error" class="text-red-500"></div>
        <form >
            <label for="email">email</label>
            <input type="email" id="email" name="email" required />
            <label for="password">Password</label>
            <input type="password" id="password" name="password" required />
            <button type="submit">Login</button>
        </form>
    </div>
</main>
</Layout>

<script >
import { navigate } from 'astro:transitions/client';
import {actions} from "astro:actions";
const form = document.querySelector('form') as HTMLFormElement;
const errorSection = document.getElementById('error') as HTMLDivElement;
const successSection = document.getElementById('success') as HTMLDivElement;

form?.addEventListener('submit', async(event) => {
  event.preventDefault();
  const formData = new FormData(form);
  try{
    const {data, error} = await actions.auth.login(formData);
    if(data?.status === 'error'){
      errorSection.innerHTML = `<p>${data.message}</p>`
    }

    if(error){
      errorSection.innerHTML = `<p>${error.message}</p>`
    }

    if(data?.status === 'success'){
      document.cookie = `access_token=${data.access_token}; Path=/; Expires=${new Date(Date.now() + 30 * 60 * 1000).toUTCString()};`;
      navigate('/dashboard')
    }
  }catch(e){
    console.log(e);
  }
})
</script>

``` login page
buoyant sparrow
true crane
#

Well you don't want it to redirect to the action

buoyant sparrow
#

no i want to redirect to the login page when the refresh token its not on cookies

#

if you dont have the refresh you dont have the access so you have to login

#

and then when you login, it redirect to dashboard

true crane
#

So you would want to return a response and redirect with your client side logic based on that response

#

here is an example from my project

if (!modifiedBody.token || modifiedBody.token !== csrfToken) {
      return new Response(
        JSON.stringify({
          code: 'FORBIDDEN',
          status: 403,
          message: 'Not authorized',
        }),
      );
    }

Then in the client side you can check if response.code is a 403 and redirect to the login screen from there

buoyant sparrow
#

so in my case should be in the action itself

true crane
#

Well no you wouldn't redirect to an action, actions are like endpoints not actual pages

The action should handle your logic for logging in if the request successfully passes your middleware check because the token is present

Otherwise you would redirect client side to the login page you have defined in src/pages

You could redirect within this if block because the 403 error will get returned by the middleware

    if(error){
      errorSection.innerHTML = `<p>${error.message}</p>`
    }
#

You could handle that in your middleware also

#

But you would again be redirecting to the login page, not the login action

#

That's the key

buoyant sparrow
#

got it

true crane
#

Though since it looks like you're login page already has error handling established

#

You could simply return the 403 response like i showed in my example above, and still render the same error message

#

no real need to redirect since it seems they are already on the login page when this is happening unless I am mistaken

buoyant sparrow
#

got it, let me try

true crane