Store session cookie from response #4511
-
Hey guys! I've been trying to build out a "native" auth solution using Laravel as my backend and Remix as my frontend. I'm currently using Laravel Sanctum for my session management on the backend for Laravel. My current issue is when making a post request to a route that Sanctum creates, it returns a Set-Cookie header that is suppose to save cookies to the client, however, since Remix is SSR, axios seems to completely ignore this and it doesn't seem like I can do anything about it. I'm assuming I want to make the get request to my server that is responding with the set-cookie header, take the cookies in the response, and use createCookieSessionStorage and save both the cookies. Unfortunately I'm not sure how to really go about this as I'm new to SSR in general. Any advice would be awesome and I'd like to use this approach if possible, thanks! Relevant links: Laravel Sanctum: https://laravel.com/docs/8.x/sanctum#introduction |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments
-
I've also tried "chaining" axios instances using .then to make a new axios request immediately after but this issue persists. I'm assuming I'm using Remix wrong for this, but I believe Remix is what I'd like to use for this project over Next as it'll have quite a few routes and the architecture fits better. Is there maybe a way to build out the auth using client only? If I can use the regular createSessionCookieStorage method I'd like to, assuming I'm going to need a Remix guru to help me figure this out. |
Beta Was this translation helpful? Give feedback.
-
Hi @robertdrakedennis and welcome to Remix! I'm a PHP developer too (Yii framework), and have been able to achieve what you're looking for, so I'll try to guide you here. Here's a first part of the answer, to get you on the right track. First off, let's start by brushing a few things:
(💡 Think of RS as a proxy, it could help. This is why CORS don't matter, you're doing server to server here) In other words:
With this as a first global picture, your login workflow should look like this: Let's say we're on Remix
NB: I would recommend trying Here's how the code could look like. Keep in mind that I don't know Sanctum, and I haven't tested this code at all. Remix Session Cookie// app/sessions.server.ts
import { createCookieSessionStorage } from "@remix-run/node";
const { getSession, commitSession, destroySession } =
createCookieSessionStorage({
cookie: {
name: "__remix_session",
httpOnly: true,
maxAge: 60,
path: "/",
sameSite: "lax",
secrets: ["s3cret1"],
secure: true,
},
});
export { getSession, commitSession, destroySession }; Your login route// app/routes/login.tsx
import { getSession, commitSession } from "~/session.server";
export async function action({request}: ActionArgs) {
const formData = await request.formData();
const login = formData.get("login");
const password = formData.get("password");
if (!login || !password) {
throw new Response("Missing parameters", {status: 400});
}
// This is Remix session
let session = await getSession(request.headers.get("cookie"));
// 1 - First, the CSRF value
const csrfL = await fetch("http://laravel/sanctum/csrf-cookie").then(res => {
console.log("Let's retrieve L CSRF cookie value");
const cookie = res.headers.get("Set-Cookie");
// .. parse the cookie to extract its value into csrf, and return it
return csrf;
});
session.set("csrf", csrfL);
// 2 - Next, let's authenticate
const tokenL = await fetch("http://laravel/sanctum/login", {
login,
password
}, {
headers: {
"X-XSRF-TOKEN": csrfL,
}
}).then(res => {
console.log("Let's retrieve L Session cookie value");
const cookie = res.headers.get("Set-Cookie");
// .. parse the cookie to extract its value into token, and return it
return token;
});
session.set("token", tokenL);
// 3 - Done, redirect to dashboard while setting the Remix cookie
return redirect("/dashboard", {
headers: {
"Set-Cookie": await commitSession(session);
}
})
}
export default Login() {
return (
<>
<h1>Login</h1>
<Form method="post">
<input type="text" name="login" />
<input type="password" name="password" />
<input type="submit" value="Login!" />
</Form>
</>
);
} Your dashboard// app/routes/dashboard.tsx
import { getSession } from "~/session.server";
export async function loader({request}: LoaderArgs) {
let session = await getSession(request.headers.get("cookie"));
let csrf = session.get("csrf");
let token = session.get("token"); // <= anytime you'll want to talk with Laravel, do it from the action/loader and pass the token in the headers
return json({
csrf,
token,
})
}
export default Dashboard() {
const { csrf, token } = useLoaderData<typeof loader>();
return (
<>
<h1>Dashboard</h1>
<p>
From Remix session: {csrf} -- {token}
</p>
</>
)
} I need to abandon my keyboard for a few hours, give this a try and get back to me with questions. |
Beta Was this translation helpful? Give feedback.
-
Actually, Mohamed Said from the Laravel team have all this covered and more in this video: |
Beta Was this translation helpful? Give feedback.
-
Firstly, thanks @robertdrakedennis for initially asking this, I came here to ask the exact same question! And thanks to @machour for a solid reply, it gave me a good base to go off and try fix my issues myself (implementing Sanctum into a remix app). I seem to be still getting 419 errors from my Using @machour's code as a starter, here's where I'm at... // login.tsx
import type { ActionArgs, LoaderArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form } from "@remix-run/react";
import { sessionStorage } from "~/sessions";
// takes a response object, gets the returned set-cookies, and filters out the XSRF-TOKEN
const getXsrfFromResponse = (response) => {
const responseCookies = (response.headers as any).raw()["set-cookie"];
const xsrfCookie = responseCookies.find((responseCookie) => {
return responseCookie.startsWith("XSRF-TOKEN");
});
const xsrfToken = xsrfCookie.replace("XSRF-TOKEN=", "").split("; ")[0];
// known gotcha is that fetch doesn't decode these, whereas axios does...
return { xsrfToken: decodeURIComponent(xsrfToken) };
};
export async function action({request}: ActionArgs) {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
if (!email || !password) {
throw new Response("Missing parameters", { status: 400 });
}
let session = await sessionStorage.getSession(request.headers.get("cookie"));
const laravelTokenResponse = await fetch("http://localhost:8000/sanctum/csrf-cookie", {
method: "get",
credentials: "include"
});
const { xsrfToken } = getXsrfFromResponse(laravelTokenResponse);
session.set("xsrfToken", xsrfToken);
const user = await fetch("http://localhost:8000/login", {
body: {
email,
password
},
method: "POST",
credentials: "include",
headers: {
"X-XSRF-TOKEN": xsrfToken,
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/json",
"Accept": "application/json"
}
}).then(res => {
console.log("login post response, always 419", res);
const cookie = res.headers.get("Set-Cookie");
return { id: 123, username: "wtf" };
});
session.set("user", user);
return redirect("/dashboard", {
headers: {
"Set-Cookie": await sessionStorage.commitSession(session)
}
})
};
export async function loader({request}: LoaderArgs) {
let session = await sessionStorage.getSession(request.headers.get("cookie"));
let user = session.get("user");
if(user) {
return redirect("/dashboard")
}
return json({})
};
export default function Screen() {
return (
<Form method="post">
<div>
<label htmlFor="email">Email</label>
<input
type="text"
name="email"
id="email"
defaultValue="[email protected]"
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
defaultValue="wtf"
/>
</div>
<button>Log In</button>
</Form>
);
} Where my SSR cookie knowledge gets a little hazy is when it comes to dealing with the set-cookies returned from |
Beta Was this translation helpful? Give feedback.
-
This thread has helped me a lot, so first of all, thank you very much. But secondly i want to add, that the provided solution in #4511 (comment) won't save us from XSRF attacks. I know it's not the main topic of this thread but in terms of security it can be misleading to read about XSRF secured communication between remix backend and laravel backend only. The communication between the remix frontend and remix backend should primarily be secured like discussed in #2906 (comment). |
Beta Was this translation helpful? Give feedback.
Hi @robertdrakedennis and welcome to Remix!
I'm a PHP developer too (Yii framework), and have been able to achieve what you're looking for, so I'll try to guide you here.
Here's a first part of the answer, to get you on the right track.
First off, let's start by brushing a few things: