あなたのコードは大丈夫ですか?

Supabaseを使っていると、こんな警告が表示されることがあります。

Using the user object as returned from supabase.auth.getSession() 
or from some supabase.auth.onAuthStateChange() events could be insecure!

この警告を無視すると、他人のデータを自由に閲覧・削除できてしまう可能性があります。

この記事では、なぜこの警告が表示されるのか、どう修正すればよいのかを説明します。

認証の基礎:セッションとは何か

まず、認証の基本を押さえておきます。

Webアプリケーションでは、「このユーザーは誰か」を識別する必要があります。たとえば、TodoアプリでAさんがログインしたとき、「このTodoはAさんのもの」と判断するためです。

Supabaseは、この情報をセッションという形で保存しています。セッションには、ユーザーIDやメールアドレスなどが含まれます。

セッション情報はどこにあるのか

セッション情報は、ブラウザのクッキーローカルストレージに保存されています。

ここで問題があります。クッキーやローカルストレージは、ユーザーが自由に編集できる場所です。つまり、悪意のあるユーザーが書き換えることができます。

警告の正体:getSession()の危険性

getSession()は何をしているのか

supabase.auth.getSession()は、ブラウザに保存されているセッション情報を、そのまま取得します。

const { data: { session } } = await supabase.auth.getSession()
const userId = session.user.id

このuserIdは、ブラウザから取得された情報です。ユーザーが書き換えていたら、別人のIDが入っている可能性があります。

実際に何が起こるのか

想像してください。

  1. あなたのTodoアプリで、AさんのIDはuser-123、BさんのIDはuser-456です
  2. Bさんが、ブラウザの開発者ツールでクッキーを開き、自分のIDをuser-123に書き換えます
  3. あなたのアプリはgetSession()で取得したIDを信じて、「この人はAさんだ」と判断します
  4. Bさんは、AさんのすべてのTodoを見たり、削除したりできてしまいます

これが、この警告が示している危険性です。

解決策:getUser()を使う

getUser()の仕組み

supabase.auth.getUser()は、ブラウザの情報をそのまま信じるのではなく、Supabaseのサーバーに問い合わせをします。

const { data: { user }, error: authError } = await supabase.auth.getUser()
const userId = user.id

サーバーは、セッションが本当に有効か、ユーザーIDが正しいかを確認してから、情報を返します。ユーザーがクッキーを書き換えても、サーバーが「この情報は正しくない」と判断します。

どちらを使うべきか

判断基準はシンプルです。

操作の種類使うべきメソッド理由
データベースへの書き込みgetUser()他人のデータを書き換えられてしまう
データベースからの読み込みgetUser()他人のデータを閲覧できてしまう
ログイン状態の確認のみgetSession()セッションの有無を見るだけなら安全
画面の表示切り替えgetSession()表示を変えるだけなら問題ない

**重要な操作にはgetUser()、単なる確認にはgetSession()**です。

修正の手順

修正が必要なファイル

Todoアプリの例で説明します。user.idを使ってデータベースを操作している関数を特定します。

  • getTodos() - Todoの一覧取得
  • createTodo() - Todo作成
  • updateTodo() - Todo更新
  • deleteTodo() - Todo削除

これらはすべてgetUser()に変更する必要があります。

修正前

export async function getTodos() {
  const supabase = await createClient()
  
  const { data: { session } } = await supabase.auth.getSession()
  if (!session) {
    return { error: '認証が必要です' }
  }
  
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .eq('user_id', session.user.id)  // 危険:改ざんの可能性
    
  return { data, error }
}

修正後

export async function getTodos() {
  const supabase = await createClient()
  
  const { data: { user }, error: authError } = await supabase.auth.getUser()
  if (authError || !user) {
    return { error: '認証が必要です' }
  }
  
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .eq('user_id', user.id)  // 安全:サーバーで確認済み
    
  return { data, error }
}

変更点は3つです。

  1. getSession()getUser()に変更
  2. sessionuserに変更
  3. エラーハンドリングでauthErrorを追加

エラーハンドリングの注意点

getUser()は、サーバーとの通信が発生します。そのため、通信エラーの可能性があります。

const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
  return { error: '認証が必要です' }
}

authErroruserの両方を確認してください。

修正しなくてよい場所

layout.tsxやmiddleware.ts

これらのファイルでgetSession()を使っている場合、ログイン状態を確認しているだけなら、修正は不要です。

const { data: { session } } = await supabase.auth.getSession()
if (!session) {
  redirect('/login')
}

セッションがあるかないかを見ているだけで、user.idを使って重要な操作をしているわけではないためです。

login/page.tsxやsignup/page.tsx

ログインページでも同様です。ログイン済みかどうかを確認して、リダイレクトする処理では、getSession()のままで問題ありません。

修正後の確認

修正が完了したら、以下を確認してください。

  1. ✅ 警告が表示されなくなった
  2. ✅ ログイン、サインアップが正常に動作する
  3. ✅ Todo の作成、更新、削除が正常に動作する
  4. ✅ ブラウザでクッキーを書き換えても、他人のデータにアクセスできない

特に4番目が重要です。開発者ツールでクッキーを編集し、意図的に不正なアクセスを試みてください。正しく修正されていれば、エラーが返されるはずです。

よくある質問

Q: 全部getUser()に変更してもいいですか?

A: 可能ですが、パフォーマンスに影響があります。getUser()はサーバーとの通信が発生するため、getSession()よりも遅くなります。単なる確認であれば、getSession()を使う方が効率的です。

Q: getSession()は使ってはいけないのですか?

A: そうではありません。ログイン状態の確認や画面の表示切り替えなど、セキュリティ上重要でない場面では、getSession()で問題ありません。

Q: この修正をしないとどうなりますか?

A: 最悪の場合、ユーザーが他人のデータを閲覧、編集、削除できてしまいます。個人情報の漏洩やデータ破壊につながる可能性があります。

まとめ

  • getSession()はブラウザの情報を直接取得するため、改ざんのリスクがある
  • getUser()はサーバーで確認するため、安全性が高い
  • データベース操作など重要な処理ではgetUser()を使う
  • 単なるログイン確認ではgetSession()でも問題ない
  • 修正後は必ず動作確認を行う

この修正により、Supabaseの警告が解消され、安全なアプリケーションになります。


参考記事

この記事の作成にあたり、以下の公式ドキュメントと記事を参考にしました。

より詳しい情報や最新のベストプラクティスについては、Supabase公式ドキュメントをご確認ください。