こんにちは!DIGGLE エンジニアの miyakawa です。
この記事は Ruby on Rails Advent Calendar 2022 の22日目の記事です。
はじめに
弊社のプロダクト DIGGLE には、Google ドライブ や Google スプレッドシートからデータをインポートする機能があり、この機能を実現するために Google Drive API を利用しています。
Google Drive API 等の Google API を利用して各種 Google サービス上のユーザーのデータを取得・操作する場合、OAuthによってアクセストークンを発行・利用する必要があります。
↓の画像のやつです
今回は Ruby on Rails のアプリにこちらの OAuth 承認フローを組み込む方法についてお話しします。
※この記事では触れないこと
- OAuth について
- Google API 個別の利用方法
- Google OAuth の検証・公開の手順
ステップ1: OAuth 同意画面の設定
まず Google Cloud の Web コンソールから OAuth 同意画面の設定を行います。
OAuth 同意画面とはユーザーが OAuth の承認をする際に表示される画面のことを指します。
下記のURLからアプリ情報等を入力していきます。
https://console.cloud.google.com/apis/credentials/consent
ひとまずは開発環境向けなので適当に項目を埋めていって問題ありません。
(本番環境向けに公開する際には正しく情報を入れる必要があります。利用する OAuth スコープによっては Google によるアプリの検証が必要になります。)
ステップ2: OAuth クライアント ID の作成
OAuth 同意画面の設定が終わったら下記 URL から OAuth クライアント ID を作成します。
https://console.cloud.google.com/apis/credentials
アプリケーションの種類は「ウェブ アプリケーション」を選択し、承認済みのリダイレクト URI に次のステップで作成する OAuth 用 Controller の callback
アクションの URI を入力します。
OAuth クライアント ID を作成するとクライアント ID とクライアントシークレットが発行されるので控えておきます。
ステップ3: Rails アプリに OAuth 承認機能を実装する
※ 既に Rails アプリの土台があることを前提にしています。
gem のインストール
今回は Google 公式の OAuth クライアントライブラリである googleauth gem を使用します。
余談
googleauth gem では signet という汎用的な OAuth 2.0 クライアントライブラリを利用しています。
もし Google 以外のサービス向けに OAuth のクライアント実装が必要な場合はこちらの利用を検討してみてください。
OAuth アクセストークンの Model 作成
今回の実装では、取得した OAuth アクセストークン(アクセストークン、リフレッシュトークン)を RDB(PostgreSQL)に保存します。
マイグレーションと Model は下記のようになります。
class CreateGoogleOauthTokens < ActiveRecord::Migration[7.0] def change create_table :google_oauth_tokens do |t| t.references :user, null: false, foreign_key: true t.string :access_token, null: false t.string :refresh_token, null: false t.timestamps end end end
class GoogleOauthToken < ApplicationRecord belongs_to :user validates :access_token, presence: true validates :refresh_token, presence: true end
※ 上記コードは説明用のため省略していますが、実際は access_token
と refresh_token
は暗号化して保存するように実装するべきです。
Token Store の作成
Token Store とは googleauth gem における OAuth アクセストークンの保存場所です。先の通り、今回は RDB です。
googleauth gem では以下二つの Token Store が実装されていますが、RDB 向けの実装は用意されていません。
- Google::Auth::Stores::FileTokenStore
- Google::Auth::Stores::RedisTokenStore
そのため、自アプリの RDB 向けの Token Store を実装します。
require 'googleauth/token_store' class DBTokenStore < Google::Auth::TokenStore def load id token = GoogleOauthToken.find_by(user_id: id) return nil if token.nil? JSON.dump({ access_token: token.access_token, refresh_token: token.refresh_token }) end def store id, token token_hash = JSON.parse(token) token = GoogleOauthToken.find_or_initialize_by(user_id: id) token.update!( access_token: token_hash['access_token'], refresh_token: token_hash['refresh_token'], ) end def delete id token = GoogleOauthToken.find_by(user_id: id) token&.destroy! end end
内容はシンプルで、load
、store
、delete
の各メソッドに DB への保存、更新、削除の処理を定義したものとなっています。
OAuth 用 Controller の作成
最後に Controller の作成です。
class GoogleOauthController < ApplicationController before_action :set_authorizer def authorize credentials = authorizer.get_credentials(current_user.id) if credentials.nil? redirect_to authorizer.get_authorization_url(request: request) else redirect_back fallback_location: root_path end end def callback cred, = authorizer.handle_auth_callback(current_user.id, request) redirect_to root_path rescue Signet::AuthorizationError render :authorization_error end private def set_authorizer # クライアント ID とクライアントシークレットを環境変数で渡しておく client_id = Google::Auth::ClientId.new(ENV.fetch('GOOGLE_OAUTH_CLIENT_ID'), ENV.fetch('GOOGLE_OAUTH_CLIENT_SECRET')) # Google API の OAuth スコープのリスト。例として Google Drive の読み込み専用スコープを指定。 scopes = ['https://www.googleapis.com/auth/drive.readonly'] token_store = DBTokenStore.new # callback アクションの URL callback_url = 'http://localhost:3000/google_oauth/callback' @authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scopes, token_store, callback_url) end end
routes.rb
の設定もお忘れなく。
... resources :google_oauth, only: [] do collection do get :authorize get :callback end end ...
※ callback アクションの URL が、ステップ2の 承認済みのリダイレクト URI に設定したものと異なる場合はリダイレクト時にエラーとなるため再度設定を確認しておいてください。
これで、authorize
アクションのURLにアクセスすることで Google OAuth の認可プロセスを行うことが可能になります。
コードの説明
まず authorizer
についてですが、これによってアクセストークンの取得・保存、認証先リダイレクトURLの作成などを行うことができます。
そして、今回利用している Google::Auth::WebUserAuthorizer は authorizer の Rack アプリケーション向けアダプタです。
@authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scopes, token_store, callback_url)
authorize
アクションは、Google OAuth の認証/認可の画面へリダイレクトする役割をもちます。
callback
アクションは、ユーザーが Google OAuth の認可を終えた後のリダイレクト先であり、リクエストに付与された認証情報を Token Store に保存します。
(参考)Google API の OAuth スコープ
Google API のサービス個別に OAuth のスコープが用意されており、下記ページでスコープの一覧を確認できます。
OAuth 2.0 Scopes for Google APIs | Authorization | Google Developers
ステップ4: アクセストークンを利用して Google API を使う
アクセストークンを取得できたら、それを利用して Google API を使うことができます。
以下は Google 製の Google ドライブ用クライアントライブラリ google-apis-drive_v3 を使う例です。
require 'google/apis/drive_v3' drive = Google::Apis::DriveV3::DriveService.new # 先述の @authorizer を利用して認証情報をセット drive.authorization = @authorizer.get_credentials(current_user.id) # ドライブトップ上のファイルを取得 files = drive.list_files() files.items.each do |file| puts file.title end
おわりに
今回は Ruby on Rails のアプリに Google の OAuth 承認フローを実装する方法についてお話ししました。
他にも関連するトピックとして、Drive API の小話や Google OAuth 利用のためのアプリの検証の方法などあるので、また機会があればお話しできればと思います。
We're hiring!
今あるものを更により良くするための方法を、我々と一緒に模索してくれる開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。
Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。