[Facebookログイン]Railsでプロフィール画像を取得したい

## はじめに

Railsで開発しているAPIアプリケーションでFacebookのプロフィール画像の取得をやってみます。

## 環境

- Mac OSX
- Rails 5.2
- ruby 2.5.1
- Facebook Graph API v3.0

RailsからGraph APIへのアクセスは[koala](https://github.com/arsduo/koala)を、FacebookのAPIキーなどの管理には[figaro](https://github.com/laserlemon/figaro)を使っています。

## 前提

- [FacebookDevelop](https://developers.facebook.com/)に登録する
- 新規アプリを作成し`AppID`と`SecretKey`を取得する
- API側は[knock](https://github.com/nsarno/knock)を使ってJWTトークンを返す

## 準備

#### アクセスキーの設定

```yml:config/application.yml
FB_APP_ID: '取得したAppID'
FB_SECRET_KEY: '取得したSecretKey'
```
#### 処理の流れ

1. アプリケーションにはFacebookのユーザーアクセストークンがPOSTされる
2. トークンの有効性を検証する
3. トークンからGraph APIにアクセスして必要なユーザー情報を取得する
4. 取得したユーザー情報を保存する

## 実装

今回はcontrollerとserviceクラスで実装しました。
色々やり方があると思いますが、以下サンプルです。

```ruby:app/controllers/facebook_controller.rb
class FacebookUserTokenController < ApplicationController
  before_action :authenticate

  def create
    render json: auth_token, status: :created
  end

  private

  def authenticate
    unless entity.present?
      raise Knock.not_found_exception_class
    end
  end

  def auth_token
    if entity.respond_to? :to_token_payload
      Knock::AuthToken.new payload: entity.to_token_payload
    else
      Knock::AuthToken.new payload: { sub: entity.id }
    end
  end

  def entity
    @entity ||=
      if FacebookService.valid_token?(auth_params[:access_token])
        data = FacebookService.fetch_data(auth_params[:access_token])
        User.find_or_create_by(uid: data['id'], provider: 'facebook') do |user|
          user.nickname = data['name']
          user.first_name = data['first_name']
          user.last_name = data['last_name']
          user.email = data['email']
          user.image_url = data.dig('picture', 'data', 'url')
          user.password = SecureRandom.urlsafe_base64
        end
      end
  end

  def auth_params
    params.require(:auth).permit :access_token
  end
end
```

```ruby:app/services/facebook_service.rb
class FacebookService
  def self.valid_token?(access_token)
    status = false
    begin
      # We need to check if the access_token is valid for our FB APP. Source: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
      debug_token = Koala::Facebook::API.new(access_token).debug_token(app_access_token_info['access_token'])
      status = true if debug_token['data']['is_valid']
    ensure
      return status
    end
  end

  def self.fetch_data(access_token)
    Koala::Facebook::API.new(access_token).get_object('me', fields: 'name,first_name,last_name,email,picture') if valid_token?(access_token)
  end

  def self.app_access_token_info
    @app_access_token ||= Koala::Facebook::OAuth.new.get_app_access_token_info
  end
end
```

controllerでServiceクラスを呼び出してトークンの検証やユーザー情報の取得をする処理を呼び出しています。
保存は`find_or_create_by`を使っています。
プロフィール画像は

```rb
user.image_url = data.dig('picture', 'data', 'url')
```
で取得しています。

_ここで`https://hogehoge.jpg`とかが返ってくればこの話は終わりでした_

#### image_urlを見てると

```ruby
puts user.image_url
# https://lookaside.facebook.com/platform/profilepic/?asid=xxxxxxxxxxxxxxxxx&height=50&width=50&ext=xxxxxxx&hash=xxxxxxxxxxx
```

普通に`.jpg`とかのURLかと思ったら違いました。
このリンクにアクセスするとブラウザだとプロフィール画像がダウンロードされました。
CDNからASIDに仕様変更したみたいです。
imgタグでも表示できましたが、このURLをこのまま保存するのもなんだかなあと・・・

## 画像をダウンロードして保存する

アプリケーション側で画像を保存するようにしてみます。

1. 取得したURLからダウンロードして画像ファイルを作成する
2. S3などにアップロードしてURLを保存する

このあたりはまた続き書きます。

## 参考

https://damelog.com/sns/facebook/urls-based-on-app-scoped-id-disabled/
https://stackoverflow.com/questions/2515931/how-can-i-download-a-file-from-a-url-and-save-it-in-rails