💿

Remixにおけるloaderの並列実行に対応するための認証チェック共通化

2024/03/29に公開

この記事では、Remixを使用する際に直面する一般的な課題の一つ、つまり未認証ユーザーのアクセス管理を共通化する方法について解説します。

背景

Remixフレームワークでは、Webページへのアクセス時に必要なデータを取得するためのloader関数が提供されています。これらのloader関数は、ページにアクセスする際にサーバー側で実行され、必要なデータをページコンポーネントへ渡す役割を担っています。しかし、Remixではこれらのloader関数が並列に実行される設計になっています。これは、レイアウトを形成するルートファイル(layout route)のloaderでユーザー認証のチェックを行ったとしても、その子ルート(child route)のloaderは親の認証チェックの結果に関わらずに実行されてしまうという挙動を意味します。そのため、アプリケーションの各所で認証チェックを記述する必要があり、コードの重複や保守性の問題を引き起こす可能性があります。

この問題を解決するためには、認証チェックのロジックを共通化し、各loaderaction関数で簡単に再利用できるようにすることが効果的です。これにより、コードの重複を避け、アプリケーションの保守性を向上させることができます。

executeWithVerifiedAccessToken 関数の実装

以下に示すのは、認証チェックロジックを共通化するためのexecuteWithVerifiedAccessToken関数のサンプルコードです。この関数はセッション情報を受け取り、ユーザーが適切に認証されているかどうかをチェックします。未認証の場合は、ログインページへリダイレクトさせる処理を行います。認証されている場合にのみ、指定されたコールバック関数(実際のビジネスロジックを含む)を実行します。

// サンプルコード: executeWithVerifiedAccessToken関数
import {redirect} from '@remix-run/server-runtime';
import type {CustomerSession} from 'server/customer-session.server';

export async function executeWithVerifiedAccessToken<T>(
  {
    session,
    redirectAfterSignInPath,
  }: {
    session: CustomerSession;
    redirectAfterSignInPath?: string;
  },
  callback: (accessToken: string) => Promise<T>,
): Promise<T> {
  // セッションからアクセストークンを取得
  const customerAccessToken = await session.getCustomerAccessToken();

  try {
    if (!customerAccessToken?.accessToken) {
      throw new Error();
    }
    // アクセストークンが有効な場合の処理を実行
    return callback(customerAccessToken.accessToken);
  } catch {
    // アクセストークンが無効な場合、ログインページにリダイレクト
    throw redirect('/login', {
      headers: {
        'Set-Cookie': await session.commit(),
      },
    });
  }
}

この関数を使用して、loaderとaction関数内での認証チェックを簡潔に行うことができます。

loaderの場合でもactionの場合でもコールバック関数内の処理はこの関数を使用しない場合の記述同様に書けば良いので利用しやすいです。

LoaderとActionでの使用例

Loader関数での使用例

export async function loader({request, context}: LoaderFunctionArgs) {
  const {session, client} = context;

  return await executeWithVerifiedAccessToken({session}, async () => {
    // 認証済みの処理
  });
}

Action関数での使用例

export async function action({request, context}: ActionFunctionArgs) {
  const {session, client} = context;

  return await executeWithVerifiedAccessToken({session}, async () => {
    // 認証済みの処理
  });
}

まとめ

Remixを使用して開発する際、コールバック関数を受け取り実行する形式の共通の認証チェックロジックを作成しておくと都度loaderに書く必要があるとはいえ認証ロジックは共通化されるので便利です。

より良い方法などあればぜひ教えていただければと思います。

P.S

RemixリポジトリのdiscussionではSingle Fetchに関しても議論されているので、実際に採用された場合はまた挙動が変わって来るかもしれませんね。
https://github.com/remix-run/remix/discussions/7640

Discussion