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
#middleware doesnt work with actions.
27 messages · Page 1 of 1 (latest)
Have you tried returning either context.redirect() or context.rewrite() instead of returning a Response?
i guess i tried but ill try again
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();
});
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?
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
<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
i dont get it i thought it has to redirect from there
Well you don't want it to redirect to the action
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
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
so in my case should be in the action itself
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
got it
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
got it, let me try
