Google APIのAccess Tokenをお手軽に取得する

Google APIの各種リソースに対する大抵の操作はREST風かつOAuth2なAPIになっていて、Access TokenとAPIのURLさえわかっていれば、例えば次のように`curl`コマンドを使って簡単にAPIを実行できます。

```shell-session
curl -H "Authorization: Bearer $ACCESS_TOKEN" $ENDPOINT
```
OAuth2はラクチンで助かりますね。ただここで必要となるAccess Tokenは OAuth2 Flow と呼ばれる手順を用いて取得する必要があり、そこを理解していない人が多そうに思いました。実はこちらもすごい簡単で、curlとブラウザだけで認可フローを実行できるのです。

# TL;DR

環境変数の設定

```shell-session
CLIENT_ID=...
CLIENT_SECRET=...
REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
SCOPE=SCOPE1%20SCOPE2%20
```

認可画面のURLを組み立て、ブラウザで開く

```shell-session
echo "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=$SCOPE&access_type=offline" 
```

ブラウザに表示されたAuthorization Codeを使ってAccess Tokenを取得する

```shell-session
AUTHORIZATION_CODE=...

curl --data "code=$AUTHORIZATION_CODE" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "redirect_uri=$REDIRECT_URI" --data "grant_type=authorization_code" --data "access_type=offline" https://www.googleapis.com/oauth2/v4/token
```

Refresh Tokenを使って新しいAccess Tokenを取得する

```shell-session
REFRESH_TOKEN=...

curl --data "refresh_token=$REFRESH_TOKEN" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token
```

# OAuth2 の認可フローの簡単な説明

登場人物は3者です。

1. Google
2. Application
3. User(Browser)

認可フローは [Google Identity Platformのリファレンス](https://developers.google.com/identity/protocols/OAuth2#scenarios)にも記述がありますが、Applicationの役割に注目すると、以下の二点を実装するだけです。

1. Applicationが、ユーザの認可を得るためのページのURLをGoogleから取得し、Browserにリダイレクトとして送信する（[Redirecting to Google's OAuth 2.0 server](https://developers.google.com/identity/protocols/OAuth2WebServer#redirecting)）
2. ユーザが認可することで生成された`Authorization Code`をGoogleに渡して `Access Token` に交換してもらう（[Handling the OAuth 2.0 server response](https://developers.google.com/identity/protocols/OAuth2WebServer#handlingresponse)）

# CLIでの手順

## Client IDの生成

[Developer ConsoleのAPI Manager](https://console.developers.google.com/apis/library)で、ClientIDを作成する必要があります。

1. New Credentials から OAuth2 client ID を選択する。
![Screen Shot 2016-01-31 at 18.17.47.png](https://qiita-image-store.s3.amazonaws.com/0/1387/ce1c6d2d-b306-8e14-2dda-f910efa658ba.png)

2. Application typeで `Other` を選択する。
![Screen Shot 2016-01-31 at 18.17.38.png](https://qiita-image-store.s3.amazonaws.com/0/1387/f51fbfcc-bbac-81c4-2dac-b77b2bb6ca2d.png)

`Application Type`は「認可フロー(Access Tokenを取得するフロー)」の方法であって、APIを叩くときには関係ありません。よくあるのは`Web Application`ですが、`Other`を選ぶと`out of band`という種類で、いわゆるDesktop Client用のフローを選択した事になります。`Web Application`でいう`redirect_ui`の値は固定値`urn:ietf:wg:oauth:2.0:oob`を使用することになります。

## Scopeの調査

使用するAPIのリファレンスを探し当て、リファレンスの"Auth" というキーワードと"Scope"というキーワードを調べるとすぐにわかります。例えばGoogle Drive APIであれば`https://www.googleapis.com/auth/drive`という値です。

## 認可画面のURLを取得する

まずは以下のように環境変数を設定します。`SCOPE`に複数の値を設定する場合は 空白文字で連結する必要があるので、空白文字をURLエンコードした値 `%20` で連結しましょう。

```shell-session
CLIENT_ID=...
CLIENT_SECRET=...
REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
SCOPE=SCOPE1%20SCOPE2%20
```

認可画面のURLを取得する…のはブラウザにまかせて、「認可画面のURLを返すENDPOINT`https://accounts.google.com/o/oauth2/v2/auth`」へのパラメータを組み立てて、そのままブラウザで開きます。
ここで組み立てたURLにアクセスすると、302が返されそのLocationヘッダの中身が「認可画面のURL」なんですが、302はそのままブラウザ任せで処理してもらうということです。

```shell-session
echo "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=$SCOPE&access_type=offline" 
```

ここでは、Refresh Tokenも取得するために`access_type=offline`も指定しています。このURLをブラウザで開くと認可画面が表示されるので、AcceptしてやるとAuthorization Codeが表示されます。

## Authorization CodeをAccess Tokenに交換する

先の手順で取得したAuthorization Codeを環境変数に設定し、https://www.googleapis.com/oauth2/v4/token を叩いてやります。

```shell-session
AUTHORIZATION_CODE=...

curl --data "code=$AUTHORIZATION_CODE" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "redirect_uri=$REDIRECT_URI" --data "grant_type=authorization_code" --data "access_type=offline" https://www.googleapis.com/oauth2/v4/token
```

これでAccess Token, Refresh Token等を取得できます。SCOPEに`openid email profile`を追加してやれば、OpenID Connectで使う id_token も取得できます。

# おまけ：

## Refresh TokenをAccess Tokenに交換する

Access Tokenは有効期限がありますが、Refresh Tokenを保存しておけばいつでも(明示的にrevokeするまでは)新しいAccess Tokenを取得できます。

```shell-session
REFRESH_TOKEN=...

curl --data "refresh_token=$REFRESH_TOKEN" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token
```

## Refresh TokenやAccess Tokenを使ってRevokeする

```shell-session
curl https://accounts.google.com/o/oauth2/revoke?token=...
```

`token`に指定する値は、Refresh Token, Access TokenどちらでもOKです。

## Access Tokenの状態を確認する

```shell-session
curl "https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=$ACCESS_TOKEN"
```

このAPIでAccess Tokenの有効期限や、認可されたSCOPEが取得できます。

なお、SCOPEが足りない（認可されていないリソース）へアクセスしようとした場合は 403 `Insufficient Permission` というエラーが返されて403の原因が少しわかりにくいので、原因不明の403を受けた場合はこのAPIを試すと良いです。
一方、同じく403ではありますが、Projectで有効にしていないAPIを叩いた場合は`Access Not Configured. The API (Notes API) is not enabled for your project...`というわかりやすいエラーが返されます。