RemixJS 2 以 session 實作登入機制
一般是以 cookie 存放 session id,session 資料存放在後端,可以是記憶體、檔案或資料庫。
但如果考慮到分散式架構,記憶體和檔案都是不可行的。
以識別用戶而言,其實資料很少一個變數值即可,其餘想放的資料頂多三四個變數值。此時使用官方範例的 createCookieSessionStorage 是個不錯的作法,將資料直接放在 cookie 並加密即可,當然除了好的加密密碼,cookie 最好設定為 httpOnly。
session 工具
使用官方的範例,同時包裝兩個方法 getMySession
和 getHeadersWithSetCookie
省點囉唆的動作。
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",
httpOnly: true,
path: "/",
sameSite: "lax",
secrets: ["your_secret_key"],
},
});
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,
};
登入頁
關於登入頁幾點請注意:
- 進入登入頁前,先判斷用戶是否已經登入,若是則轉向別的頁面。
- 表單有兩個欄位 email 和 password,email 為 userId。
- 帳號和密碼直接寫在程式碼裡是不好的作法,這只是個 demo。
- 確定登入後,將兩個值寫入 cookie,分別是 userId 和 nickname。
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 中移除。
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。