2023-10-23

RemixJS 2 以 session 實作登入機制

RemixJS 2 以 session 實作登入機制

一般是以 cookie 存放 session id,session 資料存放在後端,可以是記憶體、檔案或資料庫。 但如果考慮到分散式架構,記憶體和檔案都是不可行的。

以識別用戶而言,其實資料很少一個變數值即可,其餘想放的資料頂多三四個變數值。此時使用官方範例的 createCookieSessionStorage 是個不錯的作法,將資料直接放在 cookie 並加密即可,當然除了好的加密密碼,cookie 最好設定為 httpOnly。

session 工具

使用官方的範例,同時包裝兩個方法 getMySessiongetHeadersWithSetCookie 省點囉唆的動作。

// app/modules/sessions.ts import { Session, createCookieSessionStorage } from "@remix-run/node"; type SessionData = { userId: string; nickname: string; }; type SessionFlashData = { error: string; }; const { getSession, commitSession, destroySession } = createCookieSessionStorage<SessionData, SessionFlashData>({ cookie: { name: "__session", // domain: "remix.run", // expires: new Date(Date.now() + 60_000), httpOnly: true, // maxAge: 60, path: "/", sameSite: "lax", secrets: ["your_secret_key"], // secure: true, }, }); const getMySession = async (request: Request) => { return await getSession(request.headers.get("Cookie")); }; const getHeadersWithSetCookie = async (session: Session) => { return { "Set-Cookie": await commitSession(session), }; }; export { getSession, commitSession, destroySession, getMySession, getHeadersWithSetCookie, };

登入頁

關於登入頁幾點請注意:

  1. 進入登入頁前,先判斷用戶是否已經登入,若是則轉向別的頁面。
  2. 表單有兩個欄位 email 和 password,email 為 userId。
  3. 帳號和密碼直接寫在程式碼裡是不好的作法,這只是個 demo。
  4. 確定登入後,將兩個值寫入 cookie,分別是 userId 和 nickname。
// app/routes/login.tsx // 略 export async function loader({ request, params }: LoaderFunctionArgs) { const session = await getMySession(request); const userId = session.get("userId"); if (userId) { // 如果已經登入 const uStr = getQueryStringObject(request)["u"]; if (uStr) { return redirect(uStr); } else { return redirect("/"); } } return null; } export default function LoginPage() { // 略 return ( <> <Form method="post" encType="multipart/form-data"> {/* 略 */} <button type="submit" className="btn btn-primary"> 登入 </button> </Form> </> ); } type loginUser = { email: string; nickname: string; password: string }; export async function action({ request, params }: ActionFunctionArgs) { const users: loginUser[] = [ { email: "lsd0125@gmail.com", password: "12345678", nickname: "小嘟" }, { email: "shin@gmail.com", password: "345678", nickname: "肥肥" }, ]; const session = await getMySession(request); const body = await getBodyObject(request); const { email, password } = body; const theUser = users.find((u) => { return u.email === email && u.password === password; }); const output = { success: !!theUser, bodyData: body, }; if (theUser) { session.set("userId", email); session.set("nickname", theUser.nickname); const headers = await getHeadersWithSetCookie(session); return json(output, { headers }); } return output; }

登出功能

登出是直接把兩個值從 cookie 中移除。

// app/routes/logout.tsx import { type LoaderFunctionArgs, redirect } from "@remix-run/node"; import { getMySession, getHeadersWithSetCookie } from "~/modules/sessions"; import { getQueryStringObject } from "~/modules/handle-request-data"; export async function loader({ request, params }: LoaderFunctionArgs) { const session = await getMySession(request); session.unset("userId"); session.unset("nickname"); const headers = await getHeadersWithSetCookie(session); const uStr = getQueryStringObject(request)["u"]; if(uStr){ return redirect(uStr, { headers }); } else { return redirect("/", { headers }); } }

有 userId 後,其它要存放在後端的資料,就可以存到資料庫裡了,不要把太多資料放在 cookie 裡,通常不要超過 4K。

沒有留言:

FB 留言