Remix, Remix Authでredirect_toみたいなものを実現する
基本的にはドキュメントにとっかかりが書いてあるのでそちらも是非ご参照ください。
手っ取り早く実装方法を知りたい方は、実装するセクションをご覧ください。
前提条件
- Remix & TypeScript
- Remix Auth & remix-auth-google (GoogleStrategy)
今回の場合はremix-auth-googleを使用していますが、他のストラテジー, 認証方法でも適用可能です。
既存のコード
import { ActionFunctionArgs, redirect } from "@remix-run/node";
import { authenticator } from "~/scripts/server/user.server";
export const loader = () => redirect("/");
export const action = ({ request }: ActionFunctionArgs) => {
return authenticator.authenticate("google", request);
};
import { LoaderFunctionArgs } from "@remix-run/node";
import { authenticator } from "~/scripts/server/user.server";
export const loader = ({ request }: LoaderFunctionArgs) => {
return authenticator.authenticate("google", request, {
successRedirect: "/dashboard"
});
};
極めて一般的な実装ですね。
ここで、/invite/himitsu
を踏んでログイン or サインアップした場合、認証後に/himitsu
にリダイレクトする実装を組んでいきたいと思います。
(/invite/himitsu
を踏んでないユーザーが/himitsu
にアクセスできないようにする〜等は本記事の範囲外です。
ドキュメントを読む
上記に示してある通り、Remix Authのドキュメントにはとっかかりが書いてあります。
Custom redirect URL based on the user
Say we have /dashboard and /onboarding routes, and after the user authenticates, you need to check some value in their data to know if they are onboarded or not.
If we do not pass the successRedirect option to the authenticator.authenticate method, it will return the user data.
Note that we will need to store the user data in the session this way. To ensure we use the correct session key, the authenticator has a sessionKey property.
https://github.com/sergiodxa/remix-auth#custom-redirect-url-based-on-the-user
ざっくり翻訳すると
ユーザーに基づいたカスタムリダイレクトURL
/dashboardと/onboardingというルートがあるとします。ユーザー認証後、ユーザーデータの中の特定の値をチェックして、オンボーディングが完了しているかどうかを確認する必要があります。
authenticator.authenticateメソッドにsuccessRedirectオプションを渡さない場合、ユーザーデータが返されます。
この方法では、ユーザーデータをセッションに保存する必要があることに注意してください。正しいセッションキーを使用するために、authenticatorにはsessionKeyプロパティがあります。
ということなので、successRedirect
を指定しなければ自動リダイレクトしないので自前実装を挟む余地があるということになります。
実装する
Sessionの拡張
user
キーにはRemix Authで使用するユーザー情報を。
redirect_to
キーには主に認証時に使うリダイレクト先の情報を。
それぞれ保存します。
import { createCookieSessionStorage } from "@remix-run/node";
import type { UserData } from "~/scripts/server/user.server";
// type UserData = { user_id: string };
export const sessionStorage = createCookieSessionStorage<{
user: UserData, // 追加
redirect_to: string // 追加
}>({
cookie: {
// ...省略
}
});
export const { getSession, commitSession, destroySession } = sessionStorage;
Authenticatorの拡張
前述のSessionの拡張で指定した、ユーザー情報を格納するキー名をAuthenticator側にも指定しておきます。
// 前略
const authenticator = new Authenticator<UserData>(sessionStorage, {
sessionKey: "user" // 追加
// ☝️ session.server.ts で指定したUserDataのkey(`user`)と同じ値であること
});
// 後略
/invite/himitsu
の実装
ここら辺はかなり要件次第なのでいい感じに実装してください。
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { commitSession, getSession } from "~/scripts/server/session.server";
import { authenticator } from "~/scripts/server/user.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
// ユーザー情報を取得
const user = await authenticator.isAuthenticated(request);
// 未ログインの場合
if (!user) {
// Sessionを取得
const session = await getSession(request.headers.get("Cookie"));
// Sessionの`redirect_to`にリダイレクト先をSET
session.set("redirect_to", "/himitsu");
// requestをclone
const new_request = request.clone();
// cloneしたrequest.headersにSessionを書き込み
new_request.headers.set("Cookie", await commitSession(session));
// cloneしたrequestともに認証プロセス開始
return authenticator.authenticate("google", new_request);
}
// ログインしているユーザーが/invite/himitsuを踏んだ場合はトップページへリダイレクト
// 要件次第で調整してくだ🦏
return redirect("/");
};
/auth/google/callback
の拡張
ここも基本的にコード内に説明を書いているので追加の説明は不要だと思います。
強いて言えば、今回はSessionに入っている情報を信用する実装方針です。
ユーザーの入力値を直接redirect_to
に保存するような仕様にしてしまうと、ユーザーが思わぬ間に攻撃サイトにいる。みたいな脆弱性になるのでリダイレクト前にバリデーションをかけてください。
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
import { commitSession, getSession } from "~/scripts/server/session.server";
import { authenticator } from "~/scripts/server/user.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
// successRedirectを削除することで、自動リダイレクトを防止しユーザー情報を取得
const user = await authenticator.authenticate("google", request);
// 認証結果をセッションに保存
const session = await getSession(request.headers.get("Cookie"));
session.set(authenticator.sessionKey as "user", user);
// `redirect_to`をセッションから取得
const redirect_to = session.get("redirect_to");
session.unset("redirect_to"); // 取得したら削除
// session内容をもとにheadersを作成
const headers = new Headers({ "Set-Cookie": await commitSession(session) });
// もしも、前述の`redirect_to`が存在すれば、headersと共にリダイレクト
if (redirect_to)
return redirect(redirect_to, { headers });
// 存在しなければ規定の/dashboardへ、headersと共にリダイレクト
return redirect("/dashboard", { headers });
};
まとめ
これを拡張すれば、/invite/${id}
みたいなのも簡単に実装可能です。
参考になれば幸いです。
Discussion