Zenn
😀

remix-authを使ったログイン機能の実装

2025/02/11に公開

はじめに

この記事では、RemixのWebアプリケーションのログイン機能を実装するにあたり、remix-auth, remix-auth-formを使った実装について、説明します。

今回のソースコード

今回紹介するソースコードは、以下のGihtubのリポジトリで公開しています
https://github.com/sin-sin-shinji/remix-sample

また、記事での説明のためにコードの内容を一部修正しているため、リポジトリのコードと異なる点があることはご了承ください。

今回の実装環境

今回の実装にあたり利用したライブラリのバージョン等の環境は通りです。

  • Node.js:v22.13.0
  • @remix-run/node: 2.15.2
  • @remix-run/react: 2.15.2
  • @remix-run/serve: 2.15.2
  • remix-auth: 4.1.0
  • remix-auth-form: 3.0.0

ログイン機能の実装

ライブラリのインストール

ログイン機能の実装については、以下の公式ドキュメントを参考に実装しています。
https://github.com/sergiodxa/remix-auth?tab=readme-ov-file#usage

まず、必要なライブラリのインストールを行います。

npm install remix-auth remix-auth-form

ログイン認証ロジックの実装

次に、「ログインの認証ロジック」を行うコードを実装します。 app/services/auth.server.tsに対して、以下の様なコードを実装します。

// app/services/auth.server.ts
import { Authenticator } from 'remix-auth';
import { FormStrategy } from 'remix-auth-form';

// ログインユーザーの型定義
type User = {
  id: number;
  email: string;
  displayName: string;
}

// ログイン認証処理クラス
export const authenticator = new Authenticator<User>();

// ログイン認証ロジックの定義
authenticator.use(
  new FormStrategy(async ({ form }) => {
    // フォームデータを取得
    const email = form.get('email');
    const password = form.get('password');
    
    // Nullableチェック
    if (!email || !password) {
      throw Error('Both `email` and `password` must be required.');
    }

    // TODO: ここで対象のユーザー情報をDBから取得する処理を追加する
    // 今回はサンプルのため、ユーザー情報を作成し必ず認証成功扱いとする
    const user: User = {
      id: 1,
      email: email.toString(),
      displayName: 'Test User1',
    };
    return user;
  }),
  'user-pass'
);

ここでは、ログインフォームから受け取ったフォームのリクエストデータである「メールアドレス(email)」と「パスワード(password)」から、対応するユーザー情報が存在するかチェックし、ログインの認証処理を行います。

もし有効なユーザー情報が存在する場合は、そのユーザー情報を返すことで、認証成功とします。一方で、ユーザー情報が存在しない場合は、例外を送出することで、認証エラーと扱います。

今回の実装では、本来はデータベース等を参照して正しいユーザー情報を取得すべきですが、サンプルのためユーザー情報を作成し、必ず認証成功扱いとします。

ログインセッション情報の管理ロジックの実装

次に、「ログインセッション情報を管理するロジック」を実装します。
今回はapp/services/session.server.tsに対して、以下の様なコードを実装しました。

// app/services/session.server.ts
import { createCookieSessionStorage } from '@remix-run/node';

// SESSIONN_SECRET: 環境変数から取得する
const sessionSecret: string | undefined = process.env.SESSIONN_SECRET;
if (sessionSecret === undefined) {
  throw new Error('`SESSION_SECRET` is required.');
}

// セッション管理ロジックの実装
// 今回はcookieによる管理で実装する
export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: 'remix_sample_session',
    sameSite: 'lax',
    path: '/',
    httpOnly: true,
    secrets: [sessionSecret],
    secure: process.env.NODE_ENV === 'production',
    maxAge: 60 * 60 * 24,
  },
});

// セッション情報管理メソッド
export const { getSession, commitSession, destroySession } = sessionStorage;

ログインセッション情報を扱う上で、まず考慮すべき点はログインセッション情報の保存方法です。保存先としてブラウザの「Cookie」や、Redisなどの「Key-Value Store」や、「Database」などが挙げられます。Remixでは、様々なセッション情報の管理方法を提供していますので、詳しくは以下のドキュメントをご確認ください。
https://remix.run/docs/en/main/utils/sessions

今回は一番実装が楽である「Cookie」での実装をしました。実装は「公式ドキュメント」を参考にしています。

また、設定項目であるsecretsについては、秘匿情報にあたるため、環境変数「SESSIONN_SECRET」として管理します。

以上の設定でのCookieに保存されるログインセッション情報は以下のようになります(ChromeのDevToolで確認)。

Cookie上に保存されるログインセッション情報

ログインフォーム画面の実装

最後に、ログインフォーム画面の実装について説明します。

import { Form } from '@remix-run/react';
import { ActionFunctionArgs, redirect } from '@remix-run/node';
import { authenticator } from '~/services/auth.server';
import { sessionStorage } from '~/services/session.server';

export default function LoginPage() {
  return (
    <Form action="/login" method="post">
      <div>
        <div>
          <label htmlFor="email">メールアドレス</label>
          <input
            id="email"
            name="email"
            type="email"
            placeholder="メールアドレスを入力してください"
          />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            type="password"
            placeholder="パスワードを入力してください"
          />
        </div>
      </div>
      <button type="submit">ログイン</button>
    </Form>
  );
}

export async function action({ request }: ActionFunctionArgs) {
  // フォームから送信された情報を、ログイン認証処理で検証する
  // 認証成功したらセッション情報として、ユーザー情報を取得する
  const user = await authenticator.authenticate('user-pass', request);
  
  // 実装したセッション管理メソッドで、ユーザー情報をセッション情報に設定する
  const session = await sessionStorage.getSession(
    request.headers.get('cookie')
  );
  session.set('user', user);
  
  // レスポンスヘッダーのCookieに、先ほどのセッション情報を追記
  // ログイン成功時の画面にリダイレクトする
  throw redirect('/', {
    headers: { 'Set-Cookie': await sessionStorage.commitSession(session) },
  });
}

基本的には、ログイン認証処理で必要な情報を、フォームで送信できるように画面を実装します。

そして、フォームから送信された情報を、Remixのactionで受け取り、先ほど実装したログイン認証処理(app/services/auth.server.ts)を行います。

ログインの認証処理に成功したら、先ほど実装したセッション管理メソッド(app/services/session.server.ts)を使って、ログインユーザー情報を設定します。

最後に、レスポンスヘッダーのCookieに、ログイン情報セッションを追加し、ログイン成功時の画面にリダイレクトを行います。

終わりに

ここまで、remix-authを使ったログイン処理の実装についてとなります。

今回はログインが成功した場合の処理のみ実装していますが、実運用時は「入力値チェックのバリデーション」や、「ログイン認証エラー時のエラーハンドリング」などの機能が必要となります。次は、これらの機能についても、実装していきたいと思います。

Discussion

ログインするとコメントできます