認証機能が全修正になった話
はじめに
実務1ヶ月目、初めて任された認証機能の実装。Google OAuth認証と自社JWTの管理という、一見シンプルに見えた要件でした。
結果:ほぼ全修正
数週間かけた実装が、設計の根本的な問題により全てやり直しになりました。
この記事では、なぜそうなったのか、何を学んだのかを赤裸々に書きます。同じ轍を踏む人が一人でも減れば幸いです。
最初の設計:何が問題だったのか
採用した設計
- Google OAuthで認証
- 自社JWTをiron-sessionで管理
- バックエンドでhttpOnlyクッキーに詰めてフロントに送信
- リフレッシュトークンをDBに保存
一見問題なさそうに見えますよね?私もそう思っていました。
なぜこの設計になったのか
BEレビュアー(PM兼務)の提案:
- 「同時にYouTube APIの権限も取得したい」→ Google OAuth
- httpOnlyクッキーでのトークン送信
- 自社実装の方向性
FEレビュアーの提案:
- 元々Firebaseが念頭にあった
- Server Actionsとiron-sessionでのセッション管理
私(新人)の判断:
- 両方の提案を取り入れようとして、設計思想の不一致に気づけなかった
- httpOnlyクッキーとiron-sessionの組み合わせの問題点を理解できなかった
- 協議の場でも理解が追いつかず、問題点を見抜けなかった
具体的な問題点
-
OAuth vs OIDC の混同
- 認証(Authentication)にはOIDCを使うべきだった
- OAuthは認可(Authorization)のプロトコル
-
クッキー形式の不一致
- httpOnlyクッキーとiron-sessionという異なる仕組みの混在
- それぞれの特性を理解せずに組み合わせた
-
トークン管理の複雑化
- DBとiron-sessionの二重管理
- どこに何を保存すべきか、設計として整理されていなかった
-
BE/FEの設計思想のズレ
- それぞれの提案を「いいとこ取り」しようとして失敗
- 全体として整合性のない設計になった
失敗の本質:私が陥った3つの罠
罠1:「アウトプット至上主義」の誤解
「アウトプットしていきましょう!」という言葉を誤解していました。
「とりあえず出せば、レビュアーが弾いて導いてくれる」という他責思考に陥っていたんです。
最初の数日間、認証についてひたすらインプットしてみましたが、成果物がないことへの不安から、理解不十分なまま実装を進めてしまいました。
罠2:レビュー時の「お客さん状態」
BE・FE両レビュアー、私の3人での協議の場で:
- ほぼ両レビュアーの議論で、私は聞いているだけ
- それまでの調べ学習の成果でお話自体はギリギリ聞き取れていたが、やはり根本的な理解ができていなかったのだと思う
- 議論自体もそれぞれのバイアスがかかっていた可能性もある
- 結果、設計の不一致に誰も気づけなかった
「わからない」「これで合ってますか?」をもっと言うべきでした。
罠3:「意見を汲む」と「全部取り入れる」の混同
両方の意見を尊重するあまり、技術的に整合性のない設計になってしまいました。
「おふたりとも経験豊富だから、両方の提案を組み合わせればいいはず」
そんな安易な考えが、破綻の原因でした。
修正版:何をどう変えたのか
採用した新設計
全修正が決まり、再度作戦会議を実施。
再実装の計画を詰めていく中で、「Firebase」や「Auth0」「AWS Cognito」などの**IDaaS(Identity as a Service)**という選択肢があることも知りました。今回は利用に至りませんでしたが、これらのサービスでの実装方針も、今回の設計の参考にさせていただきました。
最終的に採用した設計:
- OAuthからOIDCへ切り替え(認証の本来の形に)
- YouTube APIのスコープは追加する形で対応
- httpOnlyクッキー → Authorizationヘッダーでのやり取りに変更
- JWTのDB保存を取りやめ、Next.jsのiron-sessionのみで管理
なぜこの設計が良かったのか
認証と認可の分離が明確に
- 認証にはOIDCを使う
- 認可(YouTube API)にはスコープ追加で対応
- それぞれの役割が明確になった
クッキーとヘッダーの混在を解消
- Authorizationヘッダーで統一
- iron-sessionでのシンプルなトークン管理
セキュリティリスクの低減
- 不要なDB保存を取りやめ
- 管理箇所を減らしてシンプルに
// Before: httpOnlyクッキーとiron-sessionの不整合
// クッキー形式が混在し、管理が複雑に
// After: Authorizationヘッダーで統一
// iron-sessionでのトークン管理のみ
const session = await getSession();
const token = session.accessToken;
headers: {
'Authorization': `Bearer ${token}`
}
意識の変化:「レビュー依存」からの脱却
Before:依存マインド
「危ういものはレビュアーが弾いて導いてくれる」
→ 他人任せ、学習不足でも進めてしまう(AIも活用しているので尚更)
After:責任マインド
「私が責任を持って調べ尽くす」
正直、時間がかかりました。やり直しの実装では、最初の数日間をほぼ情報収集に費やしました。
でも、自分で説明できるものが作れました。
得られた変化
知識面:
- 認証・認可の基礎知識が身についた(OAuth, OIDC, JWT, セッション管理...)
- セキュリティへの意識が劇的に変わった
- 技術選定の際に考慮すべき観点が見えてきた
マインド面:
- 「わからないまま進める」ことの怖さを痛感
- レビューは「チェック機構」であって「丸投げ先」ではないと理解
- 自分の実装に責任を持つ意識が芽生えた
当たり前のことですが、取り組む前後で、セキュリティや認証認可についての知識が全然違います。そして何より、取り組む意識が変わりました。
新人エンジニアへのメッセージ
「アウトプット」の本当の意味
「アウトプットしていこう」は、理解不十分でも出せという意味ではありません。
むしろ、「調べて、考えて、自分なりの解を出す」プロセスを大切にするということ。
成果物がない期間が不安なのはわかります。でも、理解してから進める方が、結果的に早くて確実です。
レビューの本質
- レビューは**「より良くするための協力」**であって、「丸投げ先」ではない
- レビュアーが全てを見抜けるわけではない
- 自分が一番の責任者という意識を持つ
協議の場では「お客さん」にならない。わからないことは素直に聞く。その勇気が、結果的にプロジェクトを守ります。
失敗から学ぶ勇気
全修正という痛い経験でしたが、ここで学んだことは今後の財産です。
- 認証・認可の深い理解
- 設計の整合性を見抜く力
- 責任を持って実装に臨む姿勢
失敗を恐れるより、失敗から何を学ぶかが大事。そして学んだことを、次に活かせばいい。
さいごに
この記事を読んで、「自分も同じことやりそう...」と思った方がいれば、それだけで書いた意味がありました。
認証機能は、アプリケーションの根幹を成す重要な部分です。だからこそ、最初の設計をちゃんと考えることが大切です。
「とりあえず動けばいい」ではなく、「なぜこの設計なのか、説明できる」状態を目指しましょう。
それが、チームのためであり、何より自分自身の成長のためになります。
最後まで読んでいただき、ありがとうございました。
この経験が、誰かの役に立てば嬉しいです。
Discussion