Remix AuthにおけるStrategyのsuccessメソッドを呼び出してもセッションデータが保存されない問題を解決する
Remix Authとは
Remixにサーバーサイドでの認証を組み込みたいときに便利なのがremix-auth
というパッケージです。
このパッケージはPassport.js
からとても強い影響を受けていて、ストラテジーベースの認証を提供しています。
カスタムストラテジーを自分で実装してみた
そしてremix-auth
ではカスタムなストラテジーを自分で実装できます。なので勉強がてらremix-auth-twitterを参考にTwitter
認証用のストラテジーを車輪の再発明してみました。
特徴としては内部でtwitter-api-v2
パッケージを採用することでユニークさ(?)を出しています。
問題が発生
しかしその過程で、authenticate
のさいごでthis.success
を呼び出しても、リダイレクト先のloader
でisAuthenticated
の返り値がnull
になってしまう問題に直面しました。
この場合、authenticate
の内部では認証が成功しているにもかかわらず、failureRedirect
に指定したパスに遷移してしまいます。
最初、authenticate
の実装方法に間違っているのかと思い、他のコミュニティのStrategy
の実装を確認してみました。
しかし、authenticate
の内部でcommitSession
を直接呼び出している様子はありませんでした。
そしてsuccess
メソッドの実装を確認してみるとわかるのですが、内部でcommitSession
を呼び出していることが確認できます。なので手動で呼び出す必要はないのです。
// remix-auth/build/strategy.js class Strategy { ... async success(user, request, sessionStorage, options) { // if a successRedirect is not set, we return the user if (!options.successRedirect) return user; let session = await sessionStorage.getSession(request.headers.get("Cookie")); // if we do have a successRedirect, we redirect to it and set the user // in the session sessionKey session.set(options.sessionKey, user); session.set(options.sessionStrategyKey || "strategy", this.name); throw server_runtime_1.redirect(options.successRedirect, { headers: { "Set-Cookie": await sessionStorage.commitSession(session) }, }); } }
次にcallback.tsx
のloader
を確認してみました。
内部で呼び出しているauthenticator.authenticate
に対して、successRedirect
を指定せずに返り値のUser型
の値を取り出してみます。
その値に対して手動でcommitSession
を呼び出しSet-Cookie
を設定することで、上記の問題の解決を図りました。
export const loader: LoaderFunction = async ({ request }) => { const user = await authenticator.authenticate("twitter", request, {}); if (user) { const session = await getSession(request); session.set("user", user.id); return redirect("/authenticated", { headers: { "Set-Cookie": await commitSession(session), }, }); } else { return redirect("/") } };
・・・が、うまくいきません。。。
UTF-8問題の可能性が浮上
色々ネットを漁っていたら、Remixのリポジトリに、FIX utf8 text content cookie problemというプルリクエストを見つけました。
要約すると、cookie
モジュールの内部で使用されているatob
関数とbtoa
関数がUTF-8
をサポートしていないため、UTF-8
を含んだ値を含むクッキーを保存し、そのクッキーを読み込もうとすると正しい結果を得ることができないとのことです。
なので、verify
コールバック関数内でフィルターされたUser
型のデータを返すとき、Twitter
のユーザー名やプロフィール等は日本語が含まれる可能性が高いので、これを含めないようにします。
代わりにid_str
やscreen_name
などの内容のみをセッションデータに保存するようにすることで上記の問題を回避できました。
// app/services/auth.server.ts ... import { sessionStorage } from "./session.server"; ... export type User = { id: number; screenName: string; }; export let authenticator = new Authenticator<User>(sessionStorage); ... authenticator.use( new TwitterStrategy( { ... }, async ({ accessToken, accessSecret, profile, context }) => { return { id: profile.id, screenName: profile.screen_name, }; } ), "twitter" );
まとめ
セッションデータにはあくまでユーザーを識別できる最小の値のみを含むようにして、loader
でそれを取り出し、使用しているデータベースからその識別子を元にユーザーデータを取り出すようにすることを心がけるように設計すると良いと思います。