在庫・出荷管理アプリを作ってます⑩:Supabaseでユーザー名ログインを実装する方法|メールアドレスと両対応する認証機能

前回の記事では、Supabaseを使った新規登録の自動化について解説しました。
今回はその続編として、ユーザー体験をさらに向上させるために 「ユーザー名でのログイン機能」 を実装しました。

この記事では、メールアドレス OR ユーザー名の両方でログインできる仕組みの構築手順を、発生したエラーやセキュリティの工夫も含めて詳しく記録します。


目次

Supabase認証にユーザー名ログインを導入する背景

現状の課題

  • メールアドレスのみのログイン:覚えにくく、入力も面倒
  • ユーザビリティの問題:特に農業従事者など、現場でのメール入力は負担が大きい

実装の目標

  • メールアドレス または ユーザー名のどちらでもログイン可能にする
  • 既存のSupabase認証システムを最大限活用
  • セキュリティを維持しつつ、ユーザビリティを改善

データベース設計とカラム変更

まずはデータベース構造を見直しました。
既存の users テーブルには未使用の company_type カラムがあったため、これを username カラムとして転用することで、余計なカラム追加を避けました。

-- カラム名を変更
ALTER TABLE users RENAME COLUMN company_type TO username;

-- ユーザー名の一意性を保証
ALTER TABLE users ADD CONSTRAINT users_username_unique UNIQUE (username);

想定外の問題:既存データの重複

UNIQUE制約を追加する際、既存のデータに重複がありエラーが発生しました。
そこで以下のように重複を調査・修正し、制約を適用しました。

-- 重複データの確認
SELECT username, COUNT(*)
FROM users
WHERE username IS NOT NULL
GROUP BY username
HAVING COUNT(*) > 1;

-- 重複データの修正
UPDATE users
SET username = username || '_' || id::text
WHERE username = '間人';

新規登録フォームにユーザー名入力を追加

新規登録時にユーザー名を受け付けるよう、フロントエンドを修正しました。

<MobileInput
  label="ユーザー名"
  id="username"
  type="text"
  placeholder="ユーザー名を入力"
  value={username}
  onChange={(e) => setUsername(e.target.value)}
  required
  disabled={isLoading}
/>

登録処理にも username を保存する処理を追記し、テストは正常に通過しました。


ユーザー名ログイン機能の実装と課題

実装の流れ

  1. 入力値を確認し、「@」がなければユーザー名と判断
  2. users テーブルからユーザー名に対応するメールアドレスを取得
  3. Supabaseの auth.signInWithPassword にメールアドレスを渡してログイン
if (!emailOrUsername.includes('@')) {
  const { data, error } = await supabase
    .from('users')
    .select('email')
    .eq('username', emailOrUsername)
    .single();

  if (error || !data) {
    setError('ユーザー名が見つかりません。');
    return;
  }

  email = data.email;
}

HTML5バリデーションの落とし穴

type="email" のままだと「@がない入力=不正」と判定されてしまうため、必ず type="text" に変更する必要がありました。


最大の課題:RLSエラーとの戦い

発生したエラー

ユーザー名検索時に以下のエラーが発生しました。

Failed to load resource: the server responded with a status of 406
Query result: null

原因は ログイン前にRLS(Row Level Security)が有効化されていたため。
つまり「認証される前に users テーブルへアクセスしようとして拒否されていた」状態です。

解決策:ログイン専用ポリシーを追加

CREATE POLICY "allow_username_lookup_for_login" ON users
FOR SELECT
USING (true);

これにより、ログイン専用のクエリは制限を突破できるようになり、最終的にログイン処理は正常に動作しました。


実装後に実現できた機能

  • 新規登録
    • メールアドレス、パスワード、ユーザー名を保存
    • ユーザー名はUNIQUE制約で重複防止
  • ログイン
    • メールアドレスまたはユーザー名でログイン可能
    • 入力値から自動判別
    • 適切なエラーメッセージを表示
  • セキュリティ
    • RLSによるアクセス制御
    • メールアドレスとユーザー名の両対応でもセキュリティを維持

実装から学んだ教訓

  1. UNIQUE制約追加前には必ず既存データを調査する
  2. RLSとログインフローは密接に関係している
  3. フロントエンドの入力タイプに注意email vs text
  4. セキュリティリスクは現実的な視点で評価する

今後の改善予定

  • UI改善
    • 「ようこそ [メールアドレス]」を「ようこそ [ユーザー名]」に変更
  • セキュリティ強化
    • 出荷履歴など、役割に応じた細かいRLSポリシーの実装

まとめ

Supabaseでユーザー名ログイン機能を実装するには、データベース設計・RLS設定・フロントエンド修正が必要でした。
今回の実装を通じて、ユーザビリティとセキュリティを両立させる設計のポイントを学ぶことができました。

Supabaseを利用した認証カスタマイズに挑戦している方にとって、本記事が参考になれば幸いです。

目次