🦉

Google OAuth 2.0をCLIで利用してみた

2022/07/04に公開

普段利用しているOAuthについて、何が行われているかを自分はふんわりとしか知らなかった。
実際にはどのように認可し、トークンを取得しているのかなどが気になったので、これは1回クライアントを実装して勉強してみようと思い、OAuthを利用するCLIを作成してみた。

作成したCLIは下記。

https://github.com/x-color/google-oauth-cli


この記事では、作成したCLIの利用方法と実装内容などについて説明していく。
実装内容に興味がある場合は、#実装内容から読んでもらうと良い思う。

利用方法

Googleへのクライアント登録

GoogleのOAuthを利用する場合、事前にGoogle Cloud Platformへアクセスし各種準備をする必要がある。

まず、「APIとサービス」→「OAuth同意画面」を選択し、必要な情報を入力していく。

※今画像は初回のときの画面ではないので、実際の画面と少し違う可能性がある。

同意画面を作成後、「APIとサービス」→「認証情報」→「認証情報を作成」→「OAuthクライアントID」を選択。


アプリケーションの種類は「デスクトップ アプリ」を選択。

生成すると、OAuthで利用する「クライアントID」と「クライアント シークレット」が表示されるのでメモしておく。(後でいくらでも表示可能)

これで利用の準備は完了。

OAuthでの認可&メールアドレスを取得する

ここからは実際にCLIで、OAuthを利用してみる。

まずは、CLIをインストールする。

$ go install github.com/x-color/google-oauth-cli@latest
$ google-oauth-cli
This command has login, logout, email commands. Please use them.

事前に取得した「クライアントID」と「クライアント シークレット」を環境変数にセットする。

$ export CLIENT_ID=<クライアントID>
$ export CLIENT_SECRET=<クライアント シークレット>

準備完了したので、OAuthでの認可を開始する。
最初にloginを利用して、トークンを発行する。実行すると、URLが出力されるのでそれにアクセスするとGoogleの認可画面が表示される。そこで対象のGoogleアカウントを選択することでCLIにAPI実行権限を許可できる。

$ google-oauth-cli login

認可処理が終わった後に下記コマンドを実行することで、アカウントのメールアドレスを取得できる。

$ google-oauth-cli email
Your email address is <YOUR ADDRESS>

最後にlogoutを実行し、トークンを破棄して終了。

$ google-oauth-cli logout

実装内容

ここからは、実際にCLIの内部で何が起きているのかについて記載していく。

OAuthでの認可

今回実装した認可プロセスは「Authorization Code Flow with Proof Key for Code Exchange (PKCE)」となっている。
この認可の流れ知りたい場合は、下記を読むと良い。
https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-proof-key-for-code-exchange-pkce#how-it-works

認可処理はこの関数で実装されている。この後は、主にこの中身について説明していく。

認可コードを受け取るためのローカルサーバの起動

まず認可を開始する前に認可サーバからのリダイレクトを受けるためのサーバを起動する。
そのサーバのハンドラは下記。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/oauth.go#L45-L76

このハンドラは、リダイレクト時に付与されている下記を取得するために利用されている。

  • code: 認可コード。これを利用してアクセストークンを発行してもらう。
  • state: CSRF対策に利用される文字列。認可リクエスト時にクライアント側で生成して送ったもの。
  • error: 認可リクエストでエラーが発生した場合に付与されている値。主にユーザーが認可しなかった(Web UIで権限付与を認めなかった)際に付与されている。詳細な値はRFC6749に記載されている。

なお、これらサーバが受け取る値の詳細については、ここに記載されている。

サーバは上記情報を受け取るためだけに必要なため、この処理が完了次第終了するようにしている。

認可リクエストの実施

サーバを起動したら認可処理を始める。
認可処理では、ユーザーは認可サーバから提示されたOAuthの同意画面を用いてアプリケーションへ権限を付与することになる。

下記は、同意画面へアクセスするための認可リクエストを生成し、ユーザーに提示する処理。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/oauth.go#L121-L138

今回の認可処理では、PKCEを利用しているので、通常のOAuthで利用されるパラメータに加えて下記を送信する。

  • code_challenge: code_verifier(ランダム文字列)をcode_challenge_method で指定した形式で変換した値
  • code_challenge_method: code_challengeを送信する際の値の形式

ちなみに、PKCEとは通常のOAuthの認可処理よりもセキュアに認可処理を実施するための仕様。
クライアント側でcode_verifierと呼ばれるランダム文字列を生成し、それを指定の形式で認可リクエスト時に認可サーバへ送信する。
その後のアクセストークン発行時にクライアントはcode_verifier を再度送信する。
サーバは認可時に受け取った値とその時に受け取った値を比較することで、トークンを発行しようとしているクライアントが本当に認可したクライアントであることを確認する。
これにより、認証コードを傍受することでのなりすましを防止している。

今回は、下記でcode_verifierなどの値を生成している。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/oauth.go#L223-L244

code_verifiergenerateRandomBytes()を用いて生成している。この値で利用できる文字が決まっているので指定の文字列からランダムに選択し文字列を生成している。
code_challengeS256形式で送信するためにgenerateRandomBytes()を利用して形式を変換している。
これら値の詳細はRFC7636に記載されている。

上記処理が実行されるとユーザーに認可リクエストのURLが提示される。それにアクセスしてもらうとGoogleのOAuth同意画面がブラウザで開くので、CLIに対してアクセス権付与を許可するとリクエスト前に立ち上げたサーバへリダレクトが行われる。
リダイレクトリクエストには認可コードなどが付与されているのでそれをサーバが取得することで、アクセストークン発行の準備が整う。

Access/Refresh Tokenの取得

認可が完了したら、次はアクセストークンを発行する。実装は下記。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/oauth.go#L140-L156

アクセストークンを発行するためには、先ほどサーバが取得した認可コードなどの情報が必要。上記実装では、ハンドラ側にチャネルを渡しているので、それ経由で値を取得している(getAuthzCode()で実施している)。また、CSRF対策でリクエスト時のstateの値とサーバが受け取ったstateの値を比較している。

トークン発行処理はoauth2.Exchange()を利用している。今までの処理で取得した認可コードやcode_verifierをまとめて認可サーバへ送信し、アクセストークンとリフレッシュトークンを発行してもらっている。

ちなみに、アクセストークンの有効期限が切れた場合のトークン更新処理は、下記でoauth2により生成されているhttp.Clientの内部処理で実行されているので、利用側は特に意識する必要がない。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/oauth.go#L172-L176

このClientを利用してリソースサーバにリクエストを送信する際にアクセストークンの有効期限を検証しており、もし切れていたらリフレッシュトークンを利用して再度アクセストークンを発行してもらっている。

ここまでの処理でトークンまで取得したのでOAuthの処理としては終了となっている。

トークンを利用したサービスへのリクエスト

あとは、実際にトークンを利用してAPIを叩いてみる処理のみ。
今回の認可では、Googleアカウントのメールアドレスを取得する権限のみを許可しているので、メールアドレスを取得を試みている。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/main.go#L110-L121

特に難しいことはしておらず、トークンを取得後に生成したClientを利用して、GoogleのAPIにアクセスしているのみ。

ログアウト

OAuthにおけるログアウトとは、手元のトークンの破棄にあたる。
なのでやっていることは、メモリ上のトークン情報の削除とトークンを保管しているファイルの削除のみ。

https://github.com/x-color/google-oauth-cli/blob/856d0551ca121032426e54ec4e3c4a99e67fa538/oauth.go#L167-L170

実装してみて

今回OAuthを利用した認可を実装してみた、基礎知識が足りずに思いの外苦労した。
実際に実装してみることで、普段利用しているOAuthのしくみを理解することにつながったので、個人的にとても勉強になった。

一点気になっているのは、デスクトップアプリケーションなどではクライアントIDやシークレットが秘匿できないので、アプリケーションのなりすましなどができる点。
OAuthではパブリッククライアントと呼ばれているこれら部類は、そのリスクは許容するしかなさそうなのが悩ましい。

Discussion