バックエンドをRailsのAPI専用モード、フロントをAngular2/4でSPA構築時、OAuth認証でFacebookログインをしようとした時に遭遇した不具合対応記録

## 前提
* Angular2/4側は特に不具合がなかったので説明を割愛します。API側のみの説明になります。
    * → ちなみに[angular2-token](https://github.com/neroniaky/angular2-token)の`.signInOAuth()`メソッドを使用して実装しました。
* 基本的に下記記事を参考にAPIを構築しております。そのため、今回遭遇した不具合に対し行った対策となる**差異部分**だけを記述していく形にします。
    * https://qiita.com/AQeNku/items/94485432184257799106
* 使用gem

```
gem 'rack-cors'

gem 'devise_token_auth'
gem 'omniauth'
gem 'omniauth-oauth2'
gem 'omniauth-facebook'
```

## 不具合
### 1. API側でFacebookのOAuth認証完了後、認証情報とユーザー情報を返す際、フロント側で認証用のポップアップウィンドウを閉じることが出来ない、かつ、送られてきた情報を取得することができない。
#### 不具合発生の流れ
1. [angular2-token](https://github.com/neroniaky/angular2-token)の`.signInOAuth()`メソッドで認証用のポップアップウィンドウが`http://localhost:3000/auth/facebook`のURLで開かれる。
2. API側がFacebookに問い合わせをし、Facebookの認証画面が返却され、ポップアップウィンドウに表示される。
3. 認証完了後、API側でユーザー認証をし、ユーザー情報と認証情報を返却する。

#### 不具合の原因
不具合の流れの3で返却された情報が`json`形式のため、[angular2-token](https://github.com/neroniaky/angular2-token)の`.signInOAuth()`メソッドではハンドリングできない。

#### 不具合の解決方法
まず、[angular2-token](https://github.com/neroniaky/angular2-token)の`.signInOAuth()`メソッドで返却された情報をハンドリングするためには、下記対象箇所で行っている`Request.credentials`のイベントをポップアップウィンドウ側でキャッチしなければ行けない。
[対象箇所](
https://github.com/neroniaky/angular2-token/blob/master/src/angular2-token.service.ts#L619)

実はgemの`devise_token_auth`で上記内容に対応する`html`が用意されている。
そのため、レスポンスに上記`html`を返すようにするだけで良い。

```app/controllers/users/omniauth_callbacks_controller.rb
module Users
  class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
    include Devise::Controllers::Rememberable

    def omniauth_success
      get_resource_from_auth_hash
      create_token_info
      set_token_on_resource
      create_auth_params

      sign_in(:user, @resource, store: false, bypass: false)

      @resource.save!
      # update_token_authをつけることでレスポンスヘッダーに認証情報を付与できる。
      update_auth_header
      yield @resource if block_given?

      render_data_or_redirect('deliverCredentials', @auth_params.as_json, @resource.as_json)
    end

    protected

    def render_data(message, data)
      @data = data.merge({
        message: message
      })

      html = File.open("app/views/devise_token_auth/omniauth_external_window.html.erb").read
      template = ERB.new(html).result(binding)
      render html: template.html_safe
    end

end
```

↑の`omniauth_external_window.html.erb`は`devise_token_auth`の`gems/devise_token_auth-0.1.42/app/views/devise_token_auth/omniauth_external_window.html.erb`をファイル指定しやすいように`app/views/devise_token_auth`にコピーしただけのもので、内容は同じものとなります。

### 2. ポップアップウィンドウにFacebook認証画面が表示されている時、「後で」等で認証をキャンセルした際、エラーが発生する。

#### 不具合発生の流れ
<img width="1124" alt="cancel.png" src="https://qiita-image-store.s3.amazonaws.com/0/145077/534e2984-cd36-cd56-14ad-71f521f50ba5.png">

<img width="929" alt="スクリーンショット 2017-11-09 19.58.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/145077/9fb1a308-4d55-088e-6eaa-99982269acc5.png">

#### 不具合の原因
認証をキャンセルすると本来パラメータに存在すべきキーの`code`が存在しないため、ミドルウェア段階でハンドリングされて、`Devise::OmniauthCallbacksController#failure`のアクションが走る。この時、flashを使用しようする記述があるが、そもそも定義されていないのでエラーとなる。

#### 不具合の解決方法
`Devise::OmniauthCallbacksController`の`failure`をオーバーライドし、`omniauth_success`のように`omniauth_external_window.html.erb`を返すようにする。

下記ファイルを作成

```config/initializers/devise.rb
Rails.application.config.to_prepare do
  Devise::OmniauthCallbacksController.class_eval do
    def failure
      @data = {
        message: 'authFailure'
      }
      html = File.open("app/views/devise_token_auth/omniauth_external_window.html.erb").read
      template = ERB.new(html).result(binding)
      render html: template.html_safe
    end
  end
end
```

本来なら`Users::OmniauthCallbacksController`で`omniauth_failure`をオーバーライドし、上記処理を定義したかったのですが、ミドルウェア段階でハンドリングされているらしく(詳しく処理を追えてない)暫定的な形で対応してしまいました。
もし方法がわかる方いましたらアドバイス頂けると幸いです。

## その他
ここに載せていないAPIorAngular2/4側の設定だったり、記述の仕方を知りたい方は気軽にご連絡頂ければと思います。