はじめに
本記事はモチベーションクラウドシリーズ Advent Calendar 2022の21日目になります。
リンクアンドモチベーションでソフトウェアエンジニアをしているnakagam3です。
最近業務の中でGoogle APIを利用する機会があったのですが、知識不足が故にかGoogle APIの認証周りを調べるのになかなか骨が折れました。今回は備忘も兼ねて調べたことをまとめてみます。
Google APIの認証方式が3種類ある
Google APIには現在3種類の認証方式が存在するそうです。
1. APIキー方式
事前に発行しておいたAPIキーという文字列を、APIリクエストに含めて送信することで認証する方式です。
手軽な方式ではありますが、今回利用したかったAPIは対応していませんでした。
これはAPIキー方式が他2つに比べてセキュアではなく、Google Maps Platform APIなど一般公開されているデータにアクセスする一部のAPIにしか適していないためだそうです。
ということで却下。
2. OAuthクライアントID方式
皆さんがお持ちのGoogleアカウントを使って認証します。その際にアカウント所有者本人に同意を求め、同意されたらアクセスを許可する、という方式です。
OAuth2.0で定義されている認可コードフローにあたります。
ユーザーアカウントを使って認証できるため試しやすく、またユーザー認証が入るので非常にセキュアというメリットもあります。この方式で実現できなくもなかったのですが、今後の運用面を考えると以下2点がネックとなったため別の方式を検討することにしました。
- 初回認証時にアクセス要求に同意する画面操作が必要になる
- 認証後もaccess token/refresh tokenの管理が必要になり、有効期限が切れた場合に1のオペレーションが再度必要になる
3. サービスアカウント方式
サービスアカウントという専用のアカウントとして認証することで、ユーザーへの認証は行わずアプリが直接サーバーにアクセスを要求する方式です。
OAuth2.0で定義されているクライアント・クレデンシャルズフローにあたります。
ユーザーへの認証が無いため、バッチ処理のようにサーバー間の通信のみで完結する場合にのみ推奨されるようです。そのため認証情報はより厳密な管理が必要となりますが、今回のケースではこちらが適しているようですので、こちらを採用することにしました。
設定方法
公式の手順を参考に発行できました。
Google Workspaceの制約
サービスアカウント方式を採用した場合、通常ならサービスアカウントに対して「ファイルを共有して」データ取得・更新をするのが標準的な使い方なのですが、今回のケースではGoogle Workspace内に存在するファイルを更新する必要がありました。
皆さんのGoogle Workspaceには「ドメイン外のユーザーへの共有制限」が設定されていることが多いかと思います。サービスアカウントのメールアドレスは<サービスアカウント名>@<プロジェクト名>.iam.gserviceaccount.com
のようなドメインになるので、この制約によってファイルを共有することができません。
この問題を解決する手段として見つけたのが「ドメイン全体の委任」という設定でした。
ドメイン全体の委任
この設定は、Google Workspace管理者から「ドメイン内のユーザーに代わって」ユーザーデータにアクセスすることを許可する、というものです。
既に管理者から許可されているため、ドメイン内のユーザーデータにアクセスする際ユーザーの同意などは不要になります。また、許可する際にスコープを指定できるため、許可された範囲外のデータにはアクセスできないようにもできます。
私はAWSで言うAssume Roleを許可する設定だと認識しました。Assume RoleはClassmethodさんの記事が分かりやすい。
設定方法
実際に設定するにはGoogle Workspaceの管理者しか実施できないため私はやっていませんが、公式ヘルプに手順が載っていました。
Ruby on Railsからサービスアカウント方式で認証する
ここまでで認証アカウントの準備ができたので、実際に認証できるか試してみましょう!
gemはこちらのgoogle-api-ruby-clientを利用しました。下記コマンドでインストールできます。
gem install google-api-client
認証に必要な情報は以下のコードで生成できます。
サービスアカウントを作った時にsecret.jsonがダウンロードできると思いますが、その中から必要な情報を環境変数にセットすることで読み込むこともできます。環境変数を使う方がシークレットをコードと分離できるため管理しやすいでしょう。
# 以下の環境変数から認証情報を取得して認証する
# "GOOGLE_CLIENT_ID",
# "GOOGLE_CLIENT_EMAIL",
# "GOOGLE_ACCOUNT_TYPE",
# "GOOGLE_PRIVATE_KEY"
credentials = Google::Auth::ServiceAccountCredentials.make_creds(scope: [
Google::Apis::DriveV3::AUTH_DRIVE,
])
また、subフィールドに特定のアカウントを指定することで、指定したアカウントとしてAPI実行ができます。ここでGoogle Workspaceのユーザーを指定する時に「ドメイン全体の委任」が効いてくるんですね。
delegated_account = "hoge@hoge.com"
credentials.update!(sub: delegated_account)
生成したcredentialsは各サービスのauthorizationにセットすれば通信の際に認証されます。
drives_service = Google::Apis::DriveV3::DriveService.new
drives_service.authorization = credentials
下記メソッドを実行すると、マイドライブの情報が取得できる=認証通った!ということなので完成です。
drives_service.get_about()
おわりに
公式のドキュメントなどあるのはあるのですが、この結論に辿り着くまでに時間がかかってしまいました。
ですが認証関係は安全にデータを扱うためにとても重要な知識です。
今後も良いプロダクトを作っていくために正しく理解できるようにしていきたいですね。