🔒

Sansanの実務インターンで認証基盤をよりセキュアにした

に公開

こんにちは、nova27(@novablog_diary)です!

夏休みにSansan株式会社の技術本部/Platform Engineering Unit/Identity Platform Groupで14.5日(オンライン1.5日+本社出社13日)インターンに参加したときの記録を残します!

過去に別のインターンの記事も書いたので、そちらもぜひ読んでいってください。
https://zenn.dev/techtrain_blog/articles/intern-nova27

インターンの概要

今回私が参加したのは「エンジニア職実務体験型有給インターン -Sansan Internship 2025-」です。
エントリー時に出した希望をもとに配属された部門で、数週間かけて与えられた課題に取り組みます。

私はPlatform Engineering UnitのIdentity Platform Groupと呼ばれるチームにお世話になりました。
Identity Platform GroupはBill Oneなどで利用する共通認証基盤を開発するチームです。

少し古いですが以下の記事が参考になります。
https://buildersbox.corp-sansan.com/entry/bill-one-in-house-auth-platform

応募したきっかけ

確か最初は魔法のスプレッドシートか何かで見かけた気がします。
夏休み後半が空いていて、セキュリティに関連した実務をやってみたいなーと思って参加しました。

普段はチマチマとCTFとかやってます。
あ、ちなみに情報処理安全確保支援士試験には今年の春に合格しました。(自慢)

https://x.com/novablog_diary/status/1940641875817648375

募集要項には「セキュリティもできるよ!(意訳)」と書いてましたし、企業理念の1つに「Premise - セキュリティと利便性を両立させる」があって、セキュリティに力入れていて良さそうだと感じました。
Premise(前提条件)という言葉に「セキュリティ?そんなのやって当たり前じゃん!」という気概を感じられてすごく良いです(小並感)

遠方に住んでいる人には交通費支給とマンスリーマンションの手配をしていただけるので、関西に住んでいる自分には大変ありがたかったです。

何をしたか

セキュリティ分野を希望して、CSIRTとかに配属されるのかな?と思ってましたが、なんと認証基盤開発をすることになりました。

まさか認証基盤開発を提案されると思っていなかったので驚きましたが、機密情報を扱う認証基盤の開発を通してセキュリティについて学べると思いましたし、OAuth/OIDCは名前だけ知っていて中身を全く知らなかったので、経験できるいい機会だと思って参加を決定しました。

結果的にキャリアを考える上で視野が広まってめっちゃ良かったです。(後述)

私の場合は面談の段階で3つのテーマを提示されて、その中で以下の課題に取り組むことにしました。

OIDCのクライアント認証をPrivate Key JWTでセキュアにする!

業務内容

OIDCについて

OIDCは、ざっくりというと外部に認証機能を委譲するための認証プロトコルです。

「Google/Twitterでログイン」みたいなボタンを見たことがあると思いますが、それを安全に実現するためのプロトコルです。
こういった認証では、SNSログイン機能を設置するサービス側(Relying Party/RPやクライアントと言います)ではパスワードなどの情報を管理せず、認証はSNS(OpenID Provider/OPや認証サーバーと言います)に任せている感じになってます。

以下のページでわかりやすく解説されています。
https://www.tohoho-web.com/ex/openid-connect.html

似たようなものにOAuthがありますが、こちらは認可のプロトコルです。
OIDCはOAuthを認証システムのために拡張したものです。

Private Key JWTとは?

クライアントのなりすましを防止するためにクライアント認証の仕組みがあります。
下の図では8でクライアント認証をおこなっています。


引用: とほほのOpenID Connect入門

クライアント認証にはいくつかの方式があり、とほほのOpenID Connect入門では「client_idclient_secretをPOSTパラメータで渡す方式(client_secret_post)」が紹介されています。

この方式では、事前にクライアントと認証サーバーでclient_secretという共通のパスワードのようなものを共有しておきます。
クライアントからリクエストを送信するときは、client_secretをそのままリクエストに含めて、認証サーバーで事前に登録されたclient_secretと一致するか確認します。

Private Key JWTもクライアント認証方式の1つであり、client_assertionという署名データをPOSTパラメータで渡す方式です。

こちらはclient_secretとは違って非対称鍵を用いており、クライアントには秘密鍵、認証サーバーには公開鍵を事前に登録しておきます。
クライアントからリクエストを送信するときは、秘密鍵で署名されたJWTをclient_assertionとしてリクエストに含めて、認証サーバーで公開鍵を用いて署名を検証します。

Private Key JWTの何が嬉しいのか?

公開鍵認証方式について一般的に述べられるメリットと同様に、「機密情報である秘密鍵を外部に漏らすリスクを最小限に抑えられる」といったことが挙げられます。

OAuth 2.0のセキュリティに関するベストプラクティスをまとめたRFC 9700というものがあるのですが、こちらの「2.5. Client Authentication」では「Private Key JWTなどの非対称暗号の使用が推奨」されています。

理由としては「認証サーバーは機密性の高い対称鍵を保存する必要がなく、鍵漏洩に対する耐性が向上する」からと書かれています。

逆にclient_secretの場合は、認証サーバーでもclient_secretを厳重に管理する必要があり、攻撃対象が増えてしまいます。

具体的なタスク

もはや体験記というよりは技術記事みたいになってきましたが、ここからは具体的な業務内容について書きます。
日報を私用メールに転送し忘れたのでざっくりとしか書けてません。

最初のほうで書いた通り、私の場合はリモート1.5日、本社出社13日でした。

事前学習

今回の技術領域は全く触れたことのない領域だったので、事前にOAuth/OIDCやその他技術スタックに関する解説資料を頂いて自習しました。

軽く目を通しといてねーという感じだったので、事細かく学習したわけではなかったです。(とはいえ話を理解できる程度には学習しておきました。)

リモート1.5日

本来は3日間の予定だったのですが、コロナウイルスに感染してしまい1.5日しか稼働できませんでした。
まあ、本社出社のときに感染しなかっただけ不幸中の幸いと言いますか...

環境構築、自己紹介、認証基盤をポチポチ触ってイメージをつかむといったことをしました。
特に書くことは無いです。

出社1~3日目

実装の方針を立てるために、まずはべた書き実装で動くように試行錯誤しました。

ドキュメントが不十分なライブラリを利用していたので、「わっかんねえなコレ...」とか思いながらソースコードを読んで、気合で実装しました。

出社4~8日目

べた書きではなくプロダクトの一部として実装するうえで必要なタスクを洗い出しました。
具体的には以下の順番でタスクに取り組むことにしました。
(ウォーターフォール的に作業したわけではなく、レビュー待ちで並行して作業した部分もあります)

  1. DBスキーマの更新
  2. 管理API(公開鍵を管理するエンドポイント)の設計
  3. 管理APIハンドリング処理の追加
  4. OIDC周りの処理実装
  5. 管理画面(管理APIを操作する画面)の更新

この5日間は3番目の最初ぐらいまでやった気がします(うろ覚え)

これまでの説明を聞いていると4番が一番大変に思われるかもしれませんが、実はこの部分はライブラリがよしなにやってくれたので、そこまで苦労しませんでした。
むしろ、DBスキーマやAPIをどういった設計にすべきか決めるのに時間が掛かりました。
この2つは後になって変更するのが大変なので...

出社9~12日目

ラスト一週間になって、「あれ?コレまずいのでは(白目)」になり、取捨選択で爆速でタスクを進めることになりました。

特にテストコードを書くときは冗長に書いてしまったり、逆に網羅的にかけていなかったりして30件はレビューコメントをいただいたのかなと記憶しています。

3Aパターンという言葉を初めて聞きました。
https://paiza.hatenablog.com/entry/2024/05/14/090000

最終日

最終日には成果発表会がありました。
15分程度発表して質疑応答って感じです。

人前に立つのが苦手で緊張のあまり息ができなることが多いのですが、そこまで大人数ではなかったこともあって落ち着いて発表できました。

余った時間で最後の実装をおこなって、何とかリリースまでもっていくことができました🎉

学び

OAuth/OIDCはもちろん、バックエンドの開発経験も少なかったので学べたことが沢山ありました。

段階を踏んで既存実装を変更する

既存のAPIエンドポイントに新しいパラメータを追加したときの話です。

APIを更新するときは、そのAPIを利用するコンポーネントの動作に影響が及ばないように、以下の手順で作業することが多いと思います。

  1. 後方互換性をもたせて修正する
  2. 依存するコンポーネントを更新する
  3. 互換性をもたすための処理を削除する(無駄なコードが増え、バグの温床になるから)

今回の場合は依存するコンポーネントが限られたため、一気に更新しても動作に影響を及ぼさないと思って、上記3つの手順を一気に実施してしまいました。
そのせいで巨大なPRになってしまい、リリース時にヒヤヒヤする状況になってしまいました。

この経験を通して段階的に実装を修正する大切さを感じました。

ユースケースを深く考えて実装する

公開鍵の管理のために新しいAPIエンドポイントを追加したときの話です。

ユースケース(使用例)を考えて実装することぐらいわかっているので、「Client Secret PostならClient Secret、Private Key JWTなら登録済みの公開鍵を確認したいよねー」と思って以下の実装にしました。
(以下は実装イメージです)

type GetClientCredentialsResponse = {
    auth_method: "client_secret_post"
    client_secret: string
} | {
    auth_method: "private_key_jwt"
    public_keys: PublicKey
}

さて、この実装にはあまりよくない点があります。

認証方法を"private_key_jwt"から"client_secret_post"に変更するケースを考えてみます。
クライアント開発者は以下の手順で作業するでしょう。

  1. 前提: auth_methodは"private_key_jwt"である
  2. 管理画面からclient_secretを確認する
  3. クライアントにclient_secretを設定する
  4. 管理画面でauth_methodを"client_secret_post"に変更する

しかし上記の実装だと、auth_methodを"client_secret_post"に変更する前の2でclient_secretを確認することができません!

この経験から、ユースケースを「深く」想像することが大切だと学びました。

感想

率直な感想

渋谷のクソでかビルで働くこと自体ワクワクしていました。
1年ほど前は表参道にあったらしいんですが、現在は渋谷サクラステージの28F~32Fにあります。

28Fまでエレベータ上がるときの感覚が東京スカイツリーでした。
オフィスに関しては下の記事の通りなので詳しくは書きません。
https://jp.corp-sansan.com/mimi/2024/11/office-tour

仕事内容

サイバーセキュリティと言えば「研究」「脆弱性診断/ペネトレ」「コンサル」「CSIRT/PSIRT」ぐらいしか考えていなかったですが、「高いセキュリティ要件が求められるプロダクトの開発」という選択肢もあることに気づけました。
自分はセキュリティもコード書くのも興味ある!って感じなので丁度良い立ち位置かもしれません。

インターンの内容自体は今まで触れたことのない技術が多くて難しかったですが、逆に短期間でこれだけのことを学べたのは良かったと思っています。
簡単な内容ならインターンで学びに行く必要もないですし。

コードを書くのに慣れなくてクソコードを量産してしまい罪悪感を感じてましたが、最終的にリリースまで無理矢理持っていけましたし褒めて貰えたことが多かったので、意外と成果出せてたのかなと思います。

会社の雰囲気

会社の雰囲気は落ち着いて仕事しやすかったです。

薄暗いオフィスで社員がびっしりと並んで座って、全員がPCに向かって黙々と作業しているドラマでよくありそうな雰囲気(主人公が社会人生活に辟易しているやつ)を勝手にイメージしていたんですが、リモートで仕事している方々も多く座席数に余裕があって、軽い感じで相談や議論しながら仕事している感じでした。

大学よりも快適な環境でした。大学はもっと座席数を増やしてくれ。

チームの雰囲気

チームの方々はとにかくフレンドリーでした。

仕事仲間として親しみやすいという次元を超えて友人のような感覚でした。(とはいえ最低限の礼儀は尽くすように意識してました。多分)
何度かランチやディナーに誘っていただき、仕事のことに限らずプライベートな話もできて楽しかったです。

フレンドリーな一方で指摘するところはしっかり指摘していただけたのはありがたかったです。
先ほど書いた学びも、相談やPRで理由と共に修正案を説明していただけたから得られたものです。

また、別の部門の方々とランチに行って仕事内容について聞いたり、将来について相談したりする機会(インターンランチ)を何度か設けていただけたのですが、どの方々からもプロダクトを良くしようという熱意を感じられました。
この環境なら自分が仮に入社しても成長し続けられると感じました。

反省点

計画を立てるのが下手

最初のほうは取り敢えず進めていく感じで全く計画を立てておらず、最終週は計画を立てて進めましたが見積もりが下手で焦って作業を進める感じになってしましました。

今後のインターンでは、「X時間でXXを終わらす」という細かい計画を立てて、振り返りの時には「なぜ予定通りに進まなかったのか」原因追究をして改善するというサイクルをしっかり回していこうと思います。

生成AIを使いこなせていない

手作業でコードを書いて「ここはAI使ったほうが早いよー」と言われた場面が度々ありました。

またコードを書かせたとしても既存の実装に馴染まないコードを出力されてレビューで指摘される場面がありました。

生成AIを使いこなせるようにプロンプトを工夫したり、別の使い方を試したりすることを意識的に実践していこうと思います。

最後に

OAuth/OIDCやPrivate Key JWTといった技術面だけでなく、開発における考え方や進め方についても多くの学びがあった3週間でした。

Sansanの皆さんには貴重な経験をさせていただき、心から感謝しております。本当にありがとうございました!

Discussion