RailsでGoogleAuthのカスタムTokenStoreを作る話

# 雑談
Oauth関係難しすぎて、2ヶ月くらいずっこけてました。
(たまにしか触らなかったせいかも...)
ようやく解決しそうです。備忘録行ってみよー

# 前提知識説明
## googleauthって何？
`googleauth`はGoogleのAPIにアクセスするための認証ライブラリです。
通常、Oauth2.0のトークンを一時的にメモリやファイルに保存しますが、**データベースに保存することで、永続的に管理できる**ようになります。

## TokenStoreって何？
Googleの`googleauth`ライブラリでは、`Google::Auth::TokenStore` というインターフェースを使って、OAuth 2.0 のトークンを保存・取得・削除できます。
ActiveRecord を使うと、データベースを TokenStore のストレージとして利用できるようになるはず。

## Tokenって何？
Tokenとは、特定のユーザーやアプリケーションが認証されたことを証明するための文字列で、おそらくセキュリティ用途。
例えば、webアプリやモバイルアプリでユーザーがログインするとTokenが生成され、サーバとのやり取りでトークンを使って認証を行います。
Google認証あたりを作っていて登場した、**アクセストークン** と **リフレッシュトークン**についてさらに説明します。
### アクセストークンって何？
**役割**：ユーザーが認証されていることを証明し、APIやサービスにアクセスするためのキーとなる存在です。このトークンを使用することでサーバと通信し、データなどにアクセスすることが可能になります。

**特徴**：有効期限が短命。期限が切れると使用できません。

### リフレッシュトークンって何？
**役割**：アクセストークンが期限切れになった後、新しいアクセストークンを取得するために使用されます。リフレッシュトークンを使用して、再度ユーザーの認証を行うことなく、アクセストークンを再発行することが可能です。

**特徴**：有効期限が長命。

## tokenを永続的に使うことできるの？
**結論**：アクセストークンは一定時間で失効するが、リフレッシュトークンを保存すれば永続的に利用できる。
- アクセストークンには有効期限がある。(Googleだと1時間)
- 期限が切れたら、新しいアクセストークンを取得する必要がある
- そのため、リフレッシュトークンを保存し、アクセストークンが切れたら自動更新しなければならない

# 実装編
Google API を Ruby からアクセスするための gem として googleauth というのがあります。これは、 OAuth2 のトークンをファイルや DB に保存しておくように作られていますが、中に含まれているトークン保存のためのクラス (TokenStore) は以下の2種類しかありません。 
- [FileTokenStore](https://github.com/googleapis/google-auth-library-ruby/blob/master/lib/googleauth/stores/file_token_store.rb)
- [RedisTokenStore](https://github.com/googleapis/google-auth-library-ruby/blob/master/lib/googleauth/stores/redis_token_store.rb)

やりたいことと少し違ったので、作ろうという話です。

まず、Tokenを保存したいので DBにTokenテーブル的なものを作成しましょう。
```sh
$ rails g model google_auth_token token_key:string client_id:string access_token:string refresh_token:string expiration_time_millis:bigint user_id:integer
$ rails db:migrate
```

データを保存する場所ができたので、次に`app/models/active_record_token_store.rb`以下の文を記入してください。

```rb
class ActiveRecordTokenStore < Google::Auth::TokenStore
  # Create a new store with the supplied file.
  #
  # @param [String, File] file
  #  Path to storage file
  def initialize(user_id)
    puts "確認0"
    @user_id = user_id
  end

  # (see Google::Auth::Stores::TokenStore#load)
  def load(id)
    puts "確認１"
    record = GoogleAuthToken.find_by! user_id: @user_id, token_key: id
    record.to_json
  end

  # (see Google::Auth::Stores::TokenStore#store)
  def store(id, token)
    puts "確認２"
    json = JSON.parse(token)
    client_id = json["client_id"]
    access_token = json["access_token"]
    refresh_token = json["refresh_token"]
    expiration_time_millis = json["expiration_time_millis"]
    record = GoogleAuthToken.find_or_initialize_by user_id: @user_id, token_key: id, client_id: client_id, access_token: access_token, refresh_token: refresh_token, expiration_time_millis: expiration_time_millis
    record.save
  end

  # (see Google::Auth::Stores::TokenStore#delete)
  def delete(id)
    record = GoogleAuthToken.find_by! user_id: @user_id, token_key: id
    record.destroy
  end
end

```

# 使い方
使いたいところで、`ActiveRecordTokenStore.new(user_id)`を書いてください。

例
```
oauth_client = Google::Auth::UserAuthorizer.new(
      Google::Auth::ClientId.from_file(token_path),
      scope,
      ActiveRecordTokenStore.new(0), 
      callback_path
    )
```

# おまけ
私が上のコードをやっても、何故かめっちゃエラーで転けてました。↓
```sh
Signet::AuthorizationError: Authorization failed.  Server message: (Signet::AuthorizationError)
{
  "error": "unsupported_grant_type",
  "error_description": "Invalid grant_type: "
}
```

DBにもtokenなど何も保存されず、空文字が入っていました。
自分の場合は、至る所に`puts`でどこまで処理できてるか確認しました。
そして結局は、route.rbが悪かったです。ssession#callbackの中の処理が実行されてませんでした。

ポカミスには気をつけましょう〜