Google Drive APIを Rubyのプログラムで実行するまでの記録④

このドキュメントをもとに進めていく。

ステップ 2: Google の OAuth 2.0 サーバーにリダイレクトする

このエラーは、Rails7から導入された、オープンリダイレクトという仕組みらしい。外部URLにリダイレクトしようとすると、UnsafeRedirectErrorが出るようになっている。
参考記事:Rails7 で入ったオープンリダイレクト対策

エラー文を読むと、pass allow_other_host: true to redirect anywayと書いてある。
Google翻訳にかけると、「とにかくリダイレクトするには、allow_other_host: true を渡します」。リダイレクトさせたい場合はこれを設定すればいいようだ。

redirect_to auth_uri, allow_other_host: true

はい、問題なくリダイレクトできました。よかった。

ステップ 3: Google がユーザーに同意を求める

ここではコードパラメータを取得できればいいので、ページは表示されなくてOK。

ステップ 4: OAuth 2.0 サーバー応答を処理する

OAuth 2.0 サーバーは、リクエストで指定された URL を使用して、アプリケーションのアクセス リクエストに応答します。

ユーザーがアクセス要求を承認すると、応答には認証コードが含まれます。ユーザーがリクエストを承認しない場合、応答にはエラー メッセージが含まれます。

出典:google-api-ruby-client

ブラウザーのアクセスバーには以下の認証コードが返ってきていた。

この部分がわからない。

OAuth 2.0 フローが完了すると、 http://localhost/oauth2callbackにリダイレクトされます。

出典:google-api-ruby-client

ステップ 5: リフレッシュ トークンとアクセス トークンの認証コードを交換する

おそらくauth_codeのところに先ほど取得した認証コードを当てはめればOKそう。
問題は、これをどこで実行するか?

auth_client.code = auth_code
auth_client.fetch_access_token!

ここまでの処理は、Railsアプリケーションのsubject_dataコントローラのindexアクションで行ってきた。
localhost:3000/subject_dataにGETリクエストを送ると実行されるようになっていた。

class SubjectDataController < ApplicationController

  def index  
    require 'google/apis/drive_v3'
    require 'google/api_client/client_secrets'
  
    # client_secret.jsonファイルを読み取ってオブジェクトを作成
    client_secrets = Google::APIClient::ClientSecrets.load
    auth_client = client_secrets.to_authorization
    auth_client.update!(
      :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
      :redirect_uri => 'http://localhost',
      :additional_parameters => {
        "access_type" => "offline",         # offline access
        "include_granted_scopes" => "true"  # incremental auth
      }
    )
  
    auth_uri = auth_client.authorization_uri.to_s
    redirect_to auth_uri, allow_other_host: true
  end

end

だが、redirect_toで別のページに飛ばして、そっちでこの状態になっている。

ここで、認証コードを何らかの変数に格納しつつ、つぎの処理(リフレッシュトークンを渡してアクセストークンと交換する)が行われるようにしなきゃいけない。

遷移先を/oauth2callbackにするとどうなる?

:redirect_uri => 'http://localhost/oauth2callback'

redirec_uri_mismatchというエラーが出た。上記のコードがどこと不一致だと言われているんだろう?

ああ、そうか。client_secrets.jsonファイルで指定したリダイレクトURLと一致してないと言われているのか。こっちではhttp://localhostと指定していたから。

"redirect_uris":["http://localhost"]

ただclient_secrets.jsonファイルを変更しただけではダメで、Goolgle CloudのOAuth 2.0クライアント設定で、承認済みのリダイレクトURIも修正した。
その結果、エラーは解消された。

アクセスバーのURLは変わったけど、画面の表示は変わらない。

サンプルコードを見ると、「/oauth2callback」にGETリクエストを送ったときに処理が実行されている。

get '/oauth2callback' do
  (省略)
  auth_client.code = request['code']
  auth_client.fetch_access_token!
  (省略)
end

ああ、そうか。Railsアプリケーションは、route.rbでそれを設定するんだった。
「/oauth2callback」にGETリクエストを送ったときになんとかコントローラのなんとかアクションで実行される、と設定すればいいのか。

違う。GETリクエストを送る先は何も「/oauth2callback」にこだわらなくていいんだ。今回は「/subject_data」にしてるから、さっきのリダイレクトURLの指定も「/subject_data」にいいのか。


サンプルコードの中のrequest['code']という部分が何を参照しているのかわからなかった。
ChatGPTに聞いて、認証コードを参照しているとわかった。

これでようやくこのif文の意味がだいたいわかってきた。

if request['code'] == nil
  auth_uri = auth_client.authorization_uri.to_s
  redirect to(auth_uri)
else
  auth_client.code = request['code']
  auth_client.fetch_access_token!
  auth_client.client_secret = nil
end

認証コードを持ってないときは認証コードを作ってから認証用URLに飛ばし、認証コードを持っていたら、認証コード(リフレッシュトークン)とアクセストークンを交換する、という感じだろう。

コードを書いて実行したがうまくいかない。
if文のどっちが実行されているか確認するため、デバッグツールを使って確認してみる。

if request['code'] == nil # 認証コードを持っていなかった場合
  auth_uri = auth_client.authorization_uri.to_s
  binding.pry
  redirect_to auth_uri, allow_other_host: true
else # 認証コードを持っている場合
  auth_client.code = request['code']
  auth_client.fetch_access_token!
end

すると、ブラウザから「http://localhost:3000/subject_data」にアクセスしたらbinding.pryが実行された。

ということは、request['code']がnil、つまり「認証コードが空」の状態だ。(まだ認証画面に行ってないので当たり前)

警告によると、request['code']よりrequest.params['code']を使うのが好ましいらしい。

ここまで3時間。


あああ、もしかして、ポートの設定をしていないから???当たった!!

リダイレクトURLの設定を'http://localhost:3000/subject_data'に変更したら、認証コードを持った場合の処理が実行された。

binding.pryで確認して、赤枠の処理が実行されていることを確認した。

認証コードが入っていることを確認した。すごくうれしい。

auth_clientの中身を見てみると、いくつかわかったことがある。
この段階では、リフレッシュトークンは作られたが、アクセストークンはまだ作られていない。

@expires_atの値は、auth_clientオブジェクトの有効期限?
有効期限は1時間となっている。

ここまで3時間半。この30分の進捗は大きい。


APIを呼び出すにはアクセストークンが必要なので、アクセストークンがなかったらダメじゃん、と思って再度binding.pryで確認してみると、無事作られていた。

これで認証コードとアクセストークンの交換まで完了した。

ただ、ドキュメントを見るとあと3行ある。
アクセストークンを取得した後、クライアントシークレットをクリアしている。

auth_client.client_secret = nil

クライアントシークレットは、Google CloudでOAuth 2.0 クライアントを作った時に発行されたものだ。

これはセキュリティ上の対策として行うのが一般的なようだ。
アクセストークンを取得できた時点でもう使わないので、漏洩しないために破棄する。

つぎに、auth_clientオブジェクトをJSON形式に変換して、sessionハッシュにcrendentialsキーに対応する値として格納している。

session[:credentials] = auth_client.to_json

いったん上記2行を追加して再度実行するとエラーになった。
ビューがないと言われている。

そりゃそうだ。作ってないから。
エラーは出たが、アクセス許可通知のメールが届いたので、認証の処理はうまくいったのだと思う。

ここまでで4時間。ここで一旦中断。
長くなったので続きは別の記事で書くことにしよう。

この記事が気に入ったらサポートをしてみませんか?