こんにちは、Gaudiyでソフトウェアエンジニアを担当しているsato(@yusukesatoo06)です。
弊社が提供するファンコミュニティプラットフォーム「Gaudiy Fanlink」において、外部サービスにAPI提供をする必要があったことから、外部連携について色々と調べて実装しました。
そこで今回は、調査からサーバ構築までのプロセスと、そこで得た学びや気づきを共有できればと思います。
1. OAuthとは
1-1. OAuthの概要
OAuthとは、Webやモバイルアプリケーションなどのクライアントアプリが、ユーザーに代わって他のウェブアプリがホストしているリソースにアクセスできるよう設計されたプロトコルです。(みなさんご存知かもしれないですが...)
参考) OAuth 2.0 とは何か、どのように役立つのか? - Auth0
OAuthを使用することで、ユーザーはクライアントアプリに対して、自身のID/パスワードを直接提供せず、他サービスとの連携を可能にします。そのため、複数のサービス間で連携を行うために非常に重要な技術となっています。
※OAuthが解決する課題については、LayerXさんの記事が参考になります。
1-2. OAuthのフロー
OAuthには認可フローがいくつか存在し、大きく以下の4つで構成されます。
- 認可コード
- インプリシット
- リソースオーナー・パスワード・クレデンシャルズ
- クライアント・クレデンシャルズ
それぞれのフローには、異なる用途やセキュリティ要件があるため、適切なフローを選択する必要があります。ここに関しては、以下の記事が詳しいです。
僕たちは、上記記事でも推奨されている認可コード + PKCE(※)パターンを採用しました。
※ 認可コード横取り攻撃に対する対策
参考) Proof Key for Code Exchange (RFC 7636) - Authlete
2. OAuthが必要な背景
2-1. 外部サービス連携
今回、OAuthが必要になった背景として、外部サービスからGaudiyのAPIをコールしたいという要求がありました。また実装に関しても、1, 2ヶ月程度で行う必要があり、かなりタイトなスケジュール前提でのAPI連携となりました。
(※ ただしビジネス的な要件でAPI連携が後ろ倒しになったため、まだ実運用には至っていません)
API連携を行う手段はいくつかあります。
- APIキーでの連携
- 独自方式での連携
- OAuthでの連携 など
今回は工期の関係や、後述する少し特殊な環境下での連携だったため、最初は1か2を選択しようと考えていました。
しかし最終的には、3のOAuth形式を採用することとなりました。その主な理由は以下です。
- クライアントサイドの実装も独自となり、エコシステムを利用できない
- 今後、別のサービスと連携する際の拡張性が乏しい
2-2. 他の連携方式との比較
1. APIキーでの連携
まず最初に検討にあがったのがAPIキーでの連携方式でした。
こちらはIP制限等と併用することで、一定のセキュリティを担保することができるかつ工数のかからない方式となります。
ただし今回、Fanlink APIをコールするクライアントが、サーバサイドだけではなくクライアントアプリも含まれる前提でした。そのため、早々に本手段は選択できないことが決定しました。
2. 独自方式での連携
前述した通り、今回の連携環境が少し特殊な関係で、独自方式の連携を検討していました。
具体的に説明すると、Fanlinkと連携サービスが同一のIdPとOIDC連携をしており、同じIdPのアカウントで作られたサービス間を連携する環境でした。
そのため、IdPで発行されたID Tokenを流用することでサービス間の連携ができないかを検討していました。
一般的な方式ではないですが、工期を考えるとOAuthをゼロから作ることが難しいため、本方式を検討しました。またセキュリティの監査会社に相談した結果、セキュリティホールとなる部分もありませんでした。ただし前述の通り、クライアントサイドも独自実装となるため、本手段も選択しませんでした。
そのため、僕たちは腹を括って短期間でOAuthの仕組みを構築することにしました。
3. OAuthの提供
3-1. 提供方式
短期間でOAuthサーバを提供する必要があるため、まずはどういった方式で提供できるかを検討しました。
その中で提供方式は大きく3つ出てきました。
- フルマネージド
- サービスプロバイダー側で提供されるOAuthサービスを利用する方式
- 例) Auth0
- ハイブリッド
- サービスプロバイダー側で提供されるAPI群を利用し、自社で認可サーバを構築し提供する方式
- 例) Authlete
- フルスクラッチ
それぞれ以下のPros/Consがありました。 (Gaudiyの開発環境に依存する理由も含まれます)
サービス | Pros | Cons |
---|---|---|
フルマネージド | 導入までの期間が圧倒的に短い。フルマネージドでの提供のため運用工数がかからない。Auth0等であればグローバルでの実績があり、セキュリティ面の担保もされている | Firebase Authをすでに導入しており、IdP機能が既存サービスと競合してしまう。 |
ハイブリッド | OAuth/OIDCに特化したサービスで、既存のIdPと競合しない。フルスクラッチに比べ運用工数がかからない。コアな部分はAPIとして提供されており、セキュリティ監査等をクリアした機能を利用できる。 | フルマネージドに比べ、一定の開発/運用工数がかかってしまう。 |
フルスクラッチ | フルマネージド / ハイブリッドと比較し、ランニングコストがサーバ費用のみ。 | 開発/運用工数がかなりかかる。セキュリティ監査など周辺工数/コストもかなりかかる。実装に問題があった際に、会社として大きなリスクを抱える。 |
3-2. 今回の選定方式
今回は前述した3つの方式のうちハイブリッド型を選択し、サービスとしてはAuthleteを採用しました。
すでに導入しているFirebase Authと競合しない点、フルスクラッチに比べると開発工期を大きく圧縮できる点が理由となります。
また開発者向けのドキュメントが充実していることや、Goで利用するライブラリも用意されていた点も決め手となりました。
4. OAuthサーバの構築
4-1. Authleteについて
では実際に、OAuthサーバを構築した際の内容に移ろうと思います。
本来であればここで苦労話を記載したいのですが、Authleteを活用したこともあり、1 ~ 2ヶ月の工期に対して意外とすんなり構築できました笑
今回採用したAuthleteは、RFCに準拠するエンドポイントをAPIとして提供していて、利用者はクライアントからのリクエストをバイパスするのみでOAuthサーバを提供できるサービスとなっています。 (詳しくはサービス概要をご覧ください)
そのため、まずは今回必要なエンドポイント群を整理するところから始めました。
4-2. 必要なエンドポイント
今回のOAuthサーバの提供にあたり、外部向けには大きく以下のエンドポイントを用意しました。
- 認可エンドポイント
- トークンエンドポイント
- リソースサーバエンドポイント
それぞれ簡単に説明していきます。
認可エンドポイント
認可エンドポイントは、連携するサービスの認証/認可処理を開始するためのエンドポイントです。一般的には連携サービスの認証画面でログイン処理を行い、OAuthの処理を開始した後に、指定したリダイレクトURLが認可コードを付与された状態で戻ってきます。
仕様についてはRFC6749 3.1に仕様が記述されており、わりと柔軟に実装することができます。
例えばパスについては、フラグメントを含めなければどのような値でも利用することができます。
詳しい仕様はAuthleteさんの記事が分かりやすいです。
トークンエンドポイント
トークンエンドポイントでは、認可エンドポイントで取得した認可コードを用いてリクエストに利用するトークンを発行します。
またリフレッシュトークンを利用してリクエストすることで、トークンのリフレッシュも行うことが可能です。
仕様についてはRFC6749 3.2に記述されています。
リソースサーバエンドポイント
リソースサーバエンドポイントは、実際に認可されたリソースにアクセスするためのエンドポイントで、提供機能に応じて用意するエンドポイントとなります。
トークンエンドポイントで発行したトークンを用いてアクセスを行い、スコープに準拠したリソースであればOAuth連携したサービスから情報取得することができます。
上記以外にも、内部で利用するためのエンドポイントをいくつか用意しておりますが、今回は割愛させていただきます。
4-3. システム構成
今回、OAuthサーバを構築した際のシステム構成は以下です。
OAuthはHTTP/1.1をベースにしたプロトコルである一方で、GaudiyのバックエンドエコシステムがgRPCベースの構成となっているため、以下の形で実現しました。
構成図の通りではありますが、ポイントは以下です。
- API Gatewayを構築し、OAuthに関する外部通信は基本API Gatewayでハンドリングすることで、プロトコル準拠の通信を実現
- 後続サービスとの通信に関してはgRPCベースで通信を行い、Gaudiyのエコシステムを流用する
- Authleteとの通信は基本的にパススルー方式とし、クライアントからのリクエストをほぼ加工せず送信することで、レスポンスも可能な範囲でそのままクライアント側に返却する
また外部公開するURL仕様やバージョニングも悩む部分が多かったです。
大手サービスが提供しているエンドポイントを調査すると以下のような仕様でした。
Google
https://accounts.google.com/o/oauth2/vX/auth
Facebook
https://www.facebook.com/vXX.X/dialog/oauth
GitHub
https://github.com/login/oauth/authorize
Twitter
https://api.twitter.com/oauth/authorize
上記のうちGoogleの形式を参考に、僕たちは以下のような設計にしました。
https://<domain>/oauth/vX/<method or resource> 例) https://<domain>/oauth/v1/authorize
API仕様が変わるケースは少ないと思いますが、外部連携を行う前提に立つとクライアント側との調整/修正が必要なため、後方互換性を担保しつつ切り替え可能にしました。
5. 開発を通じて
5-1. 開発を通じた学び/気づき
今回、OAuthサーバというRFCに厳密に準拠すべきサーバを開発して、色々と学び/気付きがありました。
まずRFCの内容が意外と解釈の余地があり、何かしらの指針がないと解釈に差が生まれる恐れがあるなと感じました。そのためAuthleteという、開発するための指針があったのは非常に助かりました。その一方で、RFCに準拠した自前の認可サーバを作る選択肢を取っていたら、途方に暮れる工数がかかっていただろうなと思います。
またこのようなプロトコルを策定した過去の技術者達のすごさを改めて感じました。Webサービスを開発しているとRFCを読む機会は少ないですが、一度RFCに準拠するサービスを作ることは非常に学びが多いなと感じました。
個人/チームとしては、GitHubのやり取りの中でRFCについて触れるなど、開発者としての成長を感じた場面もありました笑
5-2. フルスクラッチとの比較
フルスクラッチで開発した場合を想定し比較すると、半分以下の工数で実装できたのではないかと思います。工数を圧縮できた大きなポイントは以下です。
- 基本的にAuthleteの設定後、Authlete提供のAPIをコールするだけでよいため実装方針が明確
- RFCに準拠するために必要となる知識が圧倒的に少ない(とはいえ開発者としては理解しておくべきですが)
- 自分たちの開発範囲が少ないため、セキュリティ監査などの対象/工数も削減できる
- シンプルなものであればすぐに構築出来るため、動くものを確認しながら試行錯誤ができる
5-3. 今後の対応
これまでAuthleteを活用したOAuthサーバの構築に触れてきましたが、冒頭でも説明した通り、残念ながら実運用にはまだ至っていません。
本来であればOAuthスコープの設計をAPI毎に行う必要があり、スコープ設計もかなり重たい内容となる想定です。ただし今回は実運用を行うエンドポイントがなくなってしまったため、スコープ設計は後ろ倒しとなりました。
OAuthサーバの実運用を行った際は、再度スコープ周りの知見を共有できればと思います。
6. まとめ
今回はOAuthサーバの実装について、その採用背景から構築方法まで紹介させていただきました。調査した内容や実際に感じたこと含めてまとめてみたので、これからOAuthサーバの実装を検討している人の参考になれば嬉しいです。
また今後の実運用に向けて、OAuthのスコープ設計周りに知見がある人がいたらぜひお話ししてみたいです。
Gaudiyに興味を持っていただいた方は、ぜひ以下のサイトも覗いてみてください!