クリスマスから続いた“認証まわりのボヤ”が、ようやく鎮火した話し

明けましておめでとうございます。本年もどうぞよろしくお願いします。

昨年のクリスマス前後から、アプリの認証まわりで ずっと燻っていたボヤ があった。

  • 普段は問題なく動く
  • しばらく使っていると突然ログインが切れる
  • しかも 特定の条件下だけ再現する

いちばんタチが悪いタイプだ。


症状:一瞬だけ動いて、すぐ弾かれる

ある条件を満たすと、

  1. メイン画面が一瞬表示される
  2. 直後にログイン画面へ戻される
  3. サーバー側では認証エラーが出ている

どう見ても「認証情報の更新がうまくいっていない」挙動だった。


問題は“仕組み”ではなく“扱い方”だった

当初は疑った。

  • トークンの署名がおかしい?
  • 保存時に壊れている?
  • 有効期限の計算ミス?

しかし、よくよく追っていくとトークン自体は常に正しいものが生成されていた

本当の問題はそこではなかった。


真の原因(複合技)

① 認証情報の役割が曖昧だった

短命なものと長命なものがあり、「どれを・いつ・どこで使うのか」 がコード上で混線していた。

名前の曖昧さは、非同期処理と組み合わさると一気に事故る。


② 非同期処理による競合

期限切れの瞬間、

  • 複数の通信が同時に失敗
  • 同時に更新処理が走る
  • 片方は成功、片方は失敗
  • 失敗側が「ログアウト」を発火

結果として更新は成功しているのに弾かれるという最悪の体験が生まれていた。


③ サーバーとアプリの“言葉のズレ”

サーバーとアプリで同じ意味の値に違う名前を付けていた

普段は表に出ないが、
期限切れの瞬間だけ破裂する地雷だった。


解決方針(ここが肝)

✔ 役割を徹底的に分離

  • 普段使う短命な認証情報
  • 更新専用の長命な認証情報

役割を混ぜない・触らせない


✔ 更新処理の責務を最小化

更新用の通信は、

  • 長命な認証情報を確認
  • 新しい短命なものだけ返す

それ以外は一切しない。


✔ 更新処理は必ず1本にする

同時に何本も走らせない。
必ず待たせて、結果を共有する。

これだけで不安定さが一気に消えた。


鎮火を確信した瞬間

ある一連の通信ログが、毎回きれいにこの順番で並ぶようになった。

  • 認証エラー
  • 更新処理成功
  • 再確認成功
  • 本来の処理が正常通過

「鎮火完了」 と思った。


学び(未来の自分へ)

  • 認証まわりは 命名が9割
  • 更新用の情報は「返さない・触らせない」が最強
  • 非同期処理は必ず競合を疑え
  • 便利な仕組みほど、扱いを間違えると牙を剥く

おわりに

正直、長かった。
クリスマスから年を越して続いたボヤ。

でもこの一件で、

  • 認証設計の勘所
  • 非同期制御の怖さ
  • 「動いている=正しい」ではない現実

を、骨身に染みて理解できた。