OauthSwiftでRefreshing an Access Token

# OauthSwiftでRefreshing an Access Token
[既存 Rails アプリに OAuth 認証を実装する - techium](https://techium.hatenablog.com/entry/2017/07/22/120944)でOAuth認証対応したWebアプリに対してトークンのリフレッシュを行う。

## 環境
- Swift 4.1
- Xcode Version 9.3 (9E145)
- 対向Webサービス
    - [Doorkeeper - awesome OAuth2 provider for your Rails app.](https://github.com/doorkeeper-gem/doorkeeper) を使った自作サイト
- 利用OSS
    - [OAuthSwift/OAuthSwift/ Swift based OAuth library for iOS](https://github.com/OAuthSwift/OAuthSwift)

## Refreshing an Access Token
リフレッシュの仕様については以下を参照。

[RFC 6749 - The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749#section-6)

refresh_tokenが必要なので取得する。

```
@refresh_token=nil
```

refresh_token無い

## refresh_tokenの生成を有効化
Doorkeeper gem で OAuth 認可の仕組みをサポートしているが、refresh_tokenを有効にしていなかった。

`config/initializers/doorkeeper.rb`を修正する。

```diff
   # Issue access tokens with refresh token (disabled by default)
-  # use_refresh_token
+  use_refresh_token
```

デフォルトのまま使ってたけどトークンの有効期限2時間でrefresh_tokenがdisableってどう使うんだよ(デフォルトのまま使うなよ

もう一度動作を確認。

```
$ irb
>> require 'oauth2'
=> true
>> client_id     = 'hoge'
>> client_secret = 'fuga'
>> redirect_uri  = 'urn:ietf:wg:oauth:2.0:oob'
=> "urn:ietf:wg:oauth:2.0:oob"
>> site          = "https://fierce-wave-40771.herokuapp.com"
=> "https://fierce-wave-40771.herokuapp.com"
>> client = OAuth2::Client.new(client_id, client_secret, :site => site)
=> #<OAuth2::Client:0x00000000925b80 @id="hope", @secret="fuga", @site="https://fierce-wave-40771.herokuapp.com", @options={:authorize_url=>"/oauth/authorize", :token_url=>"/oauth/token", :token_method=>:post, :auth_scheme=>:request_body, :connection_opts=>{}, :connection_build=>nil, :max_redirects=>5, :raise_errors=>true}>
>> code = "foo"
>> token = client.auth_code.get_token(code, :redirect_uri => redirect_uri)
=> #<OAuth2::AccessToken:0x000000015c4120 @client=#<OAuth2::Client:0x00000000925b80 @id="hoge", @secret="fuga", @site="https://fierce-wave-40771.herokuapp.com", @options={:authorize_url=>"/oauth/authorize", :token_url=>"/oauth/token", :token_method=>:post, :auth_scheme=>:request_body, :connection_opts=>{}, :connection_build=>nil, :max_redirects=>5, :raise_errors=>true}, @auth_code=#<OAuth2::Strategy::AuthCode:0x00000000f3b380 @client=#<OAuth2::Client:0x00000000925b80 ...>>, @connection=#<Faraday::Connection:0x00000000f3b010 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v0.12.1"}, @params={}, @options=#<Faraday::RequestOptions (empty)>, @ssl=#<Faraday::SSLOptions verify=true>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x00000000f3abd8 @handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp], @app=#<Faraday::Request::UrlEncoded:0x0000000156fc60 @app=#<Faraday::Adapter::NetHttp:0x0000000156fd00 @app=#<Proc:0x0000000156fdf0@/usr/local/rvm/gems/ruby-2.3.1/gems/faraday-0.12.1/lib/faraday/rack_builder.rb:152 (lambda)>, @connection_options={}, @config_block=nil>>>, @url_prefix=#<URI::HTTPS https://fierce-wave-40771.herokuapp.com/>, @proxy=nil>>, @token="3c123b2a51d798b1880f63d5629741ee48ae8f3b716025bd4c663dccb8c807ae", @refresh_token="361c8c16bad6f9bc27a4cd0c7a3d55e1ccfdcf0cb8ff293939a81604208007c3", @expires_in=7200, @expires_at=1526609633, @options={:mode=>:header, :header_format=>"Bearer %s", :param_name=>"access_token"}, @params={"token_type"=>"bearer", "created_at"=>1526602433}>
```

refresh_tokenも取れるようになった。

## OauthSwift でリフレッシュ
やっと本題。
[OAuth 2.0 Token Expiration · OAuthSwift/OAuthSwift Wiki](https://github.com/OAuthSwift/OAuthSwift/wiki/OAuth-2.0-Token-Expiration#with-oauthswift)

これを参考に組んでみる。

```swift
_ = oauthSwift.client.get(
    URLString,
    success: { (_) in
        completion?(oauthToken, nil)
}, failure: { (error) in
    self.refresh(completion: { (token, error) in
        completion?(token, error)
    })
})
```

refreshの実装は以下のような感じ。

```swift
private func refresh(completion: ((String?, Error?) -> Void)?) {
    let keychain = Keychain(service: OauthRailsTutorial.keychainService)
    guard let refreshToken = keychain[OauthRailsTutorial.oauthRefreshTokenKey] else {
        completion?(nil, nil)
        return
    }
    oauthSwift.renewAccessToken(
        withRefreshToken: refreshToken,
        success: { [unowned self] (credential, _, _) in
            self.storeCredential(into: keychain,
                                    credential: credential)
            completion?(credential.oauthToken, nil)
        }, failure: { (error) in
            print(error)
            completion?(nil, error)
    })
}
```

これで、

取得済みのトークンが有効ならそのまま使用、期限切れならリフレッシュ

と言う実装ができた。

## その他
今回作ったものはこちら

[kfurue/RxSwiftRailsTutorial at trial-refreshing-token](https://github.com/kfurue/RxSwiftRailsTutorial/tree/trial-refreshing-token)