DIGGLE開発者ブログ

予実管理クラウドDIGGLEのプロダクトチームが、技術や製品開発について発信します

Ruby on Rails で Google API を利用するための承認フローを実装する

こんにちは!DIGGLE エンジニアの miyakawa です。
この記事は Ruby on Rails Advent Calendar 2022 の22日目の記事です。

qiita.com

はじめに

弊社のプロダクト 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 を使用します。

github.com

余談

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_tokenrefresh_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

内容はシンプルで、loadstoredelete の各メソッドに 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!

今あるものを更により良くするための方法を、我々と一緒に模索してくれる開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net