Mai
【12章】オリジナルアプリ制作〜原因不明の認証エラー。仮説と検証を繰り返して解決するまで〜
2026年06月15日
コメント
まだコメントはありません。
要約を生成中...
現在開発中のWebアプリで、
メールアドレス変更やパスワード再設定機能を実装していました。
実装自体は完了していたため、
リリース前の最終確認としてPCとスマホの両方で動作チェックを行っていました。
PCではメールアドレス変更もパスワード再設定も問題なく成功。
「これでリリースできそうだな」
と思っていたのですが、
スマホで確認したところ、
なぜかパスワード再設定だけが失敗するという現象が発生しました。
しかも、
メールは届く
リンクも開ける
再設定画面も表示される
という状態。
一見すると正常に動いているように見えるのに、
最後の更新だけ失敗します。
最初は単純な実装ミスだと思っていましたが、
調査を進めるにつれて思わぬところに原因があることが分かりました。
パソコンでは問題なく動く。
しかしスマホで試すと、
再設定メールは届く
リンクも開ける
パスワード再設定モーダルも表示される
ところが最後に
Auth session missing!というエラーが表示されて保存できない。
最初は単純にコードの書き方がおかしいのかと思いました。
最初に疑ったのはブラウザの違いでした。
私はSafariでメール送信テストをしていました。
ところが実際にリンクを開いてみると、
Gmail は Chrome で開かれる
Yahooメール はメールアプリ内ブラウザで開かれる
という状態になっていました。
つまり、
Safariでメール送信
↓
メールリンクを開く
↓
別ブラウザ(Chrome)や
メールアプリ内ブラウザで開かれるという流れです。そのため最初は、
ブラウザが切り替わったことで
セッションが引き継がれず失敗しているのでは?と考えました。
そこで
Chromeでメール送信
↓
Chromeでリンクを開くも試しました。
しかし結果は同じ。
やはり
Auth session missing!でした。
つまりブラウザだけが原因ではなさそうでした。
次に疑ったのがセッション取得処理です。
当時の実装では、
const {
data: { session },
} = await supabase.auth.getSession();でセッションを取得し、
セッションが存在したらパスワード再設定モーダルを開いていました。
ところが実際には、
モーダルは開く
↓
でも updateUser() が失敗するという状態。
つまり
「モーダルを開けること」と 「パスワード更新に必要なRecovery Sessionが存在すること」
は別問題だったわけです。
ここで新たな疑問が出てきました。
スマホでは失敗する
↓
でもPCでは成功するということです。
もしコードそのものが間違っているのであれば、
PCでも失敗するはずです。
そこで改めて調べてみると、
パスワード再設定では単純にログイン状態であることではなく、
Recovery Sessionという一時的な認証状態が必要だということが分かりました。
パスワード更新時に実行していたのは、
await supabase.auth.updateUser({
password,
});ですが、
この処理はRecovery Sessionが存在している場合のみ成功します。
つまり、
パスワード再設定画面が開く
= 認証完了ではありませんでした。
実際には、
Recovery Sessionが存在する
↓
パスワード更新成功という流れだったのです。
PCではたまたまRecovery Sessionが正常に作成されていたため成功していました。
一方でスマホでは、
メールアプリやブラウザを経由する過程で認証処理が正常に完了せず、
Recovery Sessionが作成されていない状態になっていました。
その結果、
Auth session missing!が発生していたのです。
その時点で分かったのは、
Recovery Session が作成されていない
という事実だけでした。
しかし、
なぜPCでは作成され、
スマホでは作成されないのか。
原因はまだ分かっていませんでした。
調査のためログを出してみました。
すると
error_code=otp_expired
Email link is invalid or has expiredが返ってきていました。
さらに
code: null
session: nullでした。
つまり
セッション取得失敗
↓
ではなく
↓
そもそも認証処理が成立していないという状態でした。
原因はSupabaseのメールリンク形式でした。
当初送られていたリンクは
auth/v1/verify?token=pkce_xxxxxという形式。
この形式だと
メールアプリのプレビュー
アプリ内ブラウザ
開き直し
などでリンクが消費されることがあります。
結果として
リンクを開いた
↓
認証済み扱い
↓
実際の画面ではSessionが作られていない
↓
Auth session missingという状態になっていました。
そこでメールテンプレートを変更しました。
従来
token=pkce_xxxxxだったものを、
token_hash=xxxxxを利用する形式に変更。
Recoveryページで
supabase.auth.verifyOtp({
token_hash,
type: 'recovery',
});を実行して、 Recovery Sessionを確実に作成するようにしました。
変更後は
Gmail
Yahooメール
どちらでも動作。
さらに
Safari
Chrome
をまたいだケースでも正常にパスワード更新できるようになりました。
今回一番勉強になったのは、
モーダルが開く
= 認証できているではないということです。
本当に必要だったのは
Recovery Sessionでした。
また、
セッション取得エラー
↓
コードが悪いと決めつけず、
ログを出して実際に何が返っているか確認する大切さも改めて感じました。
今回の実装で、 Supabaseの認証周りの仕組みをまた少し理解できた気がします。
要約