Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Rails 5 API + Vue.js + devise_token_authでTwitterと連携するSPAを作る(①RAILS編)

More than 1 year has passed since last update.

概要

見習いエンジニアのナツキです。RailsのAPIモードを使ってVue.jsのシンプルなSPAを作ります。チュートリアルというよりは、開発ログです。この章では、RAILS部分がメインです。まだ未完成なので随時更新していきます。

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.1
BuildVersion:   17B1003
Rails 5.2.0
ruby 2.3.1

※Qiita一件目の記事です!よろしくお願いします。

目標物

名前:hogekai
概要:Twitter上で気軽に〇〇会の開催を共有・募集することができるウェブサービス。

スクリーンショット 2018-07-03 12.38.43.png

フレームワーク・ライブラリ・Gem

Rails 5 API
devise_token_auth/omniauth/omniauth-twitter
Vue/Vuex/Vue-router/Axios
Foreman/Webpacker
Element

対象者

・RailsとVue.jsの基本を理解しているが、APIやSPAの開発にまだ慣れていない人。
・トークン認証について理解を深めたい人。
・Elementを使ってスピード感のある開発をしたい人
・hogekaiの裏側が気になる人。

目次

①Rails編
1. Vue.js用のRails API アプリを作成する(forman + webpacker)
2. Rails APIにトークン認証を実装する(devise_token_auth)
3. Rails APIにTwitter認証を実装する(device_token_auth + omniauth)
4. 本番環境にデプロイ(Heroku)

②Vue編
1. SPAの動線をつくる(Vue-router)
2. Vue.js側のトークン認証を実装する(Axios + Vuex)
3. 本番環境にデプロイ(Heroku)

③その他(Element-UI)
1. landing pageを作る
2. Event投稿機能の作成(CRUD機能/form/modal)
3. Twitterイベント共有
4. Twitterメッセージ機能の作成
5. 本番環境にデプロイ(Heroku)

[未定]RailsでTwitter共有用の画像を動的に作成する(carrierwave)
1. 画像をAWS S3に保存する。
2. 画像を動的に変更して保存する。
3. 本番環境にデプロイ(Heroku)

注意

このnoteはチュートリアルというよりは、開発ログに近いものです。僕が参考にした記事をいくつか貼っていくので、わからなくなったらそちらにアクセスしてください。※英語の記事もガンガン紹介していきます!

1. Vue.js用のRails API アプリを作成する

foremanとwebpackerを使ってAPIを実装していきます。

ボイラープレート(任意)

すでにベーシックな初期設定が済んでいるプロジェクトを見つけました。教科書通りベーシックな部分が網羅されています。テンポよく開発したい方はこれをつかうのもありです。(今回は学習もかねて一から作ります)

参考記事:Rails 5.1 + Vue.js + Vuex + vue-routerの初期設定

データベースの準備

herokuへのデプロイをスムーズに行う為に最初からpostgreqlで実装していきます。

参考記事:MacのRailsアプリでPostgreSQLを使う方法

DB設定の流れ

# PostgreSQLのインストール(MAC版)
$brew install postgresql


# バージョンの確認・インストールされたかの確認
$ postgres --version
postgres (PostgreSQL) 10.4


# データベースクラスタの作成
$initdb /usr/local/var/postgres -E utf8


# PostgreSQLの起動
# ctr + cで終了
$postgres -D /usr/local/var/postgres


# データベースの確認
$psql -l


# PostgreSQLがデフォルトで参照するデータクラスを設定
$export PGDATA=/usr/local/var/postgres


# ユーザーの作成
$createuser hogekai_admin


# ユーザーの確認
$psql -q -c'select * from pg_user' postgres


# データベースの作成
$createdb hogekai_dev -O hogekai_admin


# 新しく作ったデータベースの確認
$ psql -q -c'select * from pg_user' hogekai_dev

# ユーザー権限の確認
# ここで \du と打つとユーザーの権限を確認。
# ここで \q と打つと通常の画面に戻る。
$psql -U hogekai_admin hogekai_dev


# ユーザー権限の変更(natsukingは自分のroot_userの名前)
# ここで ALTER ROLE hogekai_admin WITH Superuserと打つとhogekai_adminにsuperuserの権限を与える。
# ALTER USER hogekai_admin WITH PASSWORD 'hogekai_password' と打つとパスワードの設定・変更。
# \du と打つとユーザーの権限を確認。
# \q と打つと通常の画面に戻る。

$psql -U natsuking hogekai_dev

ここで設定した情報はあとでRails側で使います。

Rails プロジェクトの作成

参考記事: Rails 5.1 + Vue.js の使い方

参考記事: Vue.jsとRailsでTODOアプリのチュートリアルみたいなものを作ってみた

記事の通りに進まない部分も多いです。

$rails new hogekai --webpack=vue --database=postgresql

database.ymlにデータベースの情報を書き足す

database.yml
...

development:
  <<: *default
  database: hogekai_dev
  username: hogekai_admin
  password: hogekai_password

PostgreSQLを起動(もし既に動いていない場合)

$postgres -D /usr/local/var/postgres

foremanを追加(RailsとVue両方の開発サーバーを管理するgem)

// Gemfile

group :development do
 ...
 gem 'foreman'
end
$bundle install

Procfileの作成(foremanの設定書のようなもの。root directory以下に作る)

// Procfile

rails: bundle exec rails server
webpack: ./bin/webpack-dev-server
foremanを起動するコマンド
$ foreman start

この状態でhttp://localhost:5000/にアクセスすると、RailsのHello World画面が表示されるはずです。

picture_pc_04b8e7667cd2fa6902cff4b1a911f6e7.png

次に、ルートパスで表示されるViewとControllerを作成します。

$rails g controller home about
routes.rb
Rails.application.routes.draw do
  root 'home#about'

end

これでもう一度localhost:5000にアクセスすると下の様な画面が表示されるはずです。

picture_pc_f722307965f4469cfa6a520be1cf59f8.png

今度はデフォルトで生成されるapp.vueファイルを表示させて見ましょう。

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Hogekai</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'hello_vue' %>
  </head>

  <body>
    <div id='hello'>
      {{message}}
      <app></app>
    </div>
  </body>
</html>

hello_vue.jsには、いろいろとコードが書かれていますが、必要なコードだけコメントインして、あとは削除。(Turbolinkを使いたい人は、一番下のコードを代わりにコメントイン。)

hello_vue.js
import Vue from 'vue/dist/vue.esm'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#hello',
    data: {
      message: "Can you say hello?"
    },
    components: { App }
  })
})

この時点で、下の画面が表示されたら成功です。

picture_pc_1eecb888f0c92b73d714cac27d1a0d4f.png

2. Rails APIにトークン認証を実装する

hogekaiでは、twitter認証しか使いませんが、普通のパスワード認証の実装も説明していきます。

参考記事: devise token auth を使って簡単に早くAPIを作る 1

必要な部分だけ参照。

Gemのインストール

// Gemfile
...
gem 'omniauth'
gem 'devise'
gem 'devise_token_auth'
gem 'rack-cors'
$bundle install

devise_token_authをインストール&User modelの作成

$rails g devise_token_auth:install User auth

migrationファイルの編集

confirmableの部分を今回はコメントアウトします。

migration_fileXXX.rb
## db/migrate/生成されたファイルの名前

class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table(:users) do |t|
      ## Required
      t.string :provider, :null => false, :default => "email"
      t.string :uid, :null => false, :default => ""

      ## Database authenticatable
      t.string :encrypted_password, :null => false, :default => ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at
      t.boolean  :allow_password_change, :default => false

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, :default => 0, :null => false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at

      ## User Info
      t.string :name
      t.string :nickname
      t.string :image
      t.string :email

      ## Tokens
      t.json :tokens

      t.timestamps
    end

    add_index :users, :email,                unique: true
    add_index :users, [:uid, :provider],     unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,       unique: true
  end
end
$rails db:migrate

認証用のAPI controllerを作成

$ rails g controller api/v1/auth/registrations
registrations_controller.rb
// controllers/api/v1/auth/registrations_controller.rb

module Api
  module V1
    module Auth
      class RegistrationsController < DeviseTokenAuth::RegistrationsController

        private
        def sign_up_params
          params.permit(:name, :email, :password, :password_confirmation)
        end

        def account_update_params
          params.permit(:name, :email)
        end

      end
    end
  end
end

パスを調整

routes.rb
Rails.application.routes.draw do
 namespace :api, defaults: { format: :json } do
   scope :v1 do
     mount_devise_token_auth_for 'User', at: 'auth', controllers: {
         registrations: 'api/v1/auth/registrations'
     }
   end
 end
 root 'home#about'
end

パスを確認 api/v1/auth/registrations#newとかが表示されれば成功

$rails routes

initializerの設定

devise_token_auth.rb
## config/initializers/devise_token_auth.rb

 ## falseに設定
 config.change_headers_on_each_request = false

 ## コメントイン 1.monthでもよい
 config.token_lifespan = 2.weeks 


## コメントイン
 config.headers_names = {:'access-token' => 'access-token',
                         :'client' => 'client',
                         :'expiry' => 'expiry',
                         :'uid' => 'uid',
                         :'token-type' => 'token-type' }

CSRF対策(これがないとAPIリクエストでエラーがでる)

application_controller.rb
class ApplicationController < ActionController::Base
 include DeviseTokenAuth::Concerns::SetUserByToken
 protect_from_forgery
end

Postman

実際にAPIのリクエストを出して、jsonが返って来ることを確認していきます。今回はpostmanを使います。

参考記事: APIの開発がむちゃくちゃ捗る「Postman」の使い方

今回テストするJSONデータ

{
    "name": "山口なつき",
    "email": "hogekai@example.com",
    "password": "123456789"
}

上記のデータを下のスクショの様にセットしてSendをクリック。

気をつける点:
1 POSTに設定してある?
2 http://localhost:5000/api/v1/auth
3 JSON(application/json)を選択してる?

picture_pc_c53961726b5c2632e9fa2f9a226dd5fd.png

次のように結果が返ってくればOK

picture_pc_566146b20d4bbb11881231a796cf9fd9.png

ログイン

これでUserが登録されました。次はログインです。

{
"email": "hogekai@example.com",
"password": "123456789",
"password_confirmation": "123456789"
}
この内容を先ほどと同じ様にPOSTします。ただし今回はlocalhost:5000/api/v1/auth/sign_inにリクエストを送ります。

picture_pc_267a0078e5f4e58116c9c4925821f80c.png

こんな感じのものが返ってきたら成功です。ここで重要なのが、返ってきたHeadersの情報です。クリックした中身を確認します。

picture_pc_5bc8e4380bab4921b4b66ab284819577.png

  • access-token
  • client
  • uid

の三つの値をメモります。これこそが、ユーザーのログイン状態を保つための重要な情報です。

picture_pc_da3489e8ddee32f65d00fb52d0da12f7.png

パスワードの変更

パスワードの変更に挑戦します。先ほどログインして返ってきたヘッダーの情報を使います。

access-token: C3e3Ok6aJzSE5TFG0N3-bg
client: cNOPygTl4G9Px0-uFoz6Zw
uid: hogekai@example.com

今度は, localhost:5000/api/v1/auth/passwordにPUTします。

今回はPOSTではなくPUTです。

{
    "password": "987654321",
    "password_confirmation": "987654321"
}

picture_pc_23c3649ae650a1d2112d4034611eae7c.png

picture_pc_6fad9b6c90431337d7c892b020cf37d2.png

これでうまくいくと、下の様なJSONが返ってきます。

picture_pc_4eb3b86696a1d3ae034f91823f610792.png

ユーザー情報の変更

今度はユーザー情報を変更してみます。localhost:5000/api/v1/auth に PUT。headerには、前回と同じuid, client, access-tokenを入れています。名前をひらがなから漢字に変えます。

{
    "name": "山口 夏生"
}

picture_pc_77aeab518a6cd4321ec26e514260717d.png

picture_pc_4eb3b86696a1d3ae034f91823f610792.png

これで下のJSONが返ってきたら成功。

picture_pc_97adba662be87a4ff2ba8946ff60d013-2.png

パスワードを忘れた際の変更処理は、割愛します。参考記事を読んでください。

3. Rails APIにTwitter認証を実装する

パスワード認証ができたので、twitter認証にうつります。device_token_auth + omniauthで実装していきます。twitterのトークン認証の記事は地味に少なくて、苦労しました。

omniauth omniauth_callbacks_controllerソースコード
omniauth-twitter 公式ドキュメント
参考記事: Rails5のAPIモードでdevise_token_authとomniauthを使ったログインを試す

Gemのインストール

dotenvは、APIのSECRET等の情報を安全に管理するために使います。好みです。

// Gemfile 

gem 'omniauth-twitter'
gem 'dotenv-rails', '~> 2.1', '>= 2.1.1'
$bundle install
// 古いバージョンのdotenvがインストールされる可能性があるので
$bundle update

Twitterアプリとdotenv

参考記事:【Rails】『dotenv』で環境変数を管理する方法

Twitter アプリの登録+そこで入手するAPP KEYとAPP SECRETが必要になるので、記事を参考に登録を進めてください。

参考記事:リニューアル後のTwitterアプリの作成、登録法 APIキーの取得法

僕のはこんな感じ。localhostでは通りませんでした。
本番環境用のアプリもあとで登録するので、hogekai_devに変えました。

picture_pc_220c4f2095be694ef0a31f6fe510b1d6.png

そこで入手した情報を.envファイルの中に書き込みます。

// .env

TWITTER_KEY = "自分のConsumer Key"
TWITTER_SECRET = "自分のConsumer Secret"

initializerの設定

initializer以下にomniauth.rbというファイルを作成して初期設定を書き込みます。

initializer/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV.fetch("TWITTER_KEY"), ENV.fetch("TWITTER_SECRET"), callback_url: "http://127.0.0.1/api/v1/auth/twitter/callback"
end

モデルの編集

モデルにも設定を付け加えます。

user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable, :omniauth_providers: [:twitter]
twitter認証用のルートを追加します。

パスの調整

routes.rb
Rails.application.routes.draw do

  namespace :api, defaults: { format: :json } do
    scope :v1 do
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
          registrations: 'api/v1/auth/registrations',
          omniauth_callbacks: 'api/v1/auth/omniauth_callbacks'
      }
    end
  end
  root 'home#about'

end

omniauth_callbacks_controllerの上書き

callback用のcontrollerをcontrollers/api/v1/auth/以下に作成します。もともとあるコードを上書きしています。このコードが最善のコードなのかはかなり怪しいですが、色々と試行錯誤しているうちにこれにたどり着きました。あとで説明するコードもあるのでコメントを参考にしてください。

omniauth_callbakcs_controller.rb
module Api
  module V1
    module Auth
      class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController

        // ①セッションを有効化
        skip_before_action :skip_session

        def redirect_callbacks
          super
        end
        def omniauth_success
          super
          update_auth_header
        end
        def omniauth_failure
          super
        end

        protected
            // credentialを保存
          def get_resource_from_auth_hash
            super
            @resource.credentials = auth_hash["credentials"]
            clean_resource
          end

          def render_data_or_redirect(message, data, user_data = {})
            if Rails.env.production?
              if ['inAppBrowser', 'newWindow'].include?(omniauth_window_type)
                render_data(message, user_data.merge(data))
              elsif auth_origin_url
                redirect_to DeviseTokenAuth::Url.generate(auth_origin_url, data.merge(blank: true))
              else
                fallback_render data[:error] || 'An error occurred'
              end
            else
               // わかりやすい様に開発時はjsonとして結果を返す
              render json: @resource, status: :ok
            end
          end

          // twitterから取得する絵文字を取り払うメソッドたち DBエラーが起きるときにコメントイン
          // mysqlだと起きやすい
          def clean_resource
            @resource.name = strip_emoji(@resource.name)
            @resource.nickname = strip_emoji(@resource.nickname)
          end
          def strip_emoji(str)
            str.encode('SJIS', 'UTF-8', invalid: :replace, undef: :replace, replace: '').encode('UTF-8')
          end
      end
    end
  end
end

credentialsコラムの追加

Twitter APIを経由してタイムラインとかに投稿したいことを想定して、twitter認証で返って来るアクセストークンをcredentialsというコラムに保存します。

credentialsコラムを作ります。

$rails g migration addColumnToUsers credentials:text
$rails db:migrate

application.rbを編集。

application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Hogekai
  class Application < Rails::Application


    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore

    config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins '*'
        resource '*',
                 :headers => :any,
                 :expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
                 :methods => [:get, :post, :options, :delete, :put]
      end
    end
  end
end

application_controller.rbを編集

application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  protect_from_forgery
  before_action :skip_session

  protected
    def skip_session
      request.session_options[:skip] = true
    end
end

これで準備は整ったはずです。謎なコードも多いかと思いますが、公式ドキュメントやグーグルの記事を読んで理解してください。

ブラウザーで確認

ではブラウザーから確認して見ましょう。http://127.0.0.1:5000/api/v1/auth/twitter でアクセスします。

こんな感じの画面がでるはずです。

picture_pc_1280ae96cb80f28f2eba526aad5396a6.png

これでauthorize appをクリックして下の様なJSONが返ってくれば成功です。

picture_pc_e888e45225e3b54ca4d34efa90c28fe0.png

ヘッダーの確認

パスワード認証の時と同じでヘッダーにはログイン状態をキープするためのトークン情報(uid, client, access-token)が入ってます。Google Chromeの開発ツールを使うと確認できます。

参考記事:Chromeデベロッパー・ツールを使ってヘッダー情報を確認する

picture_pc_a5acbd639042d649cbc718d3714fb2cf.png

これでTwitter認証もできました。今回作ったAPIをherokuでデプロイして見ましょう。

4. 本番環境にデプロイ

API側での認証機能は実装できたので、一度herokuにデプロイして本番環境で稼働してみます。
herokuのアカウントを持っていることを前提で進めます。

参考記事:挫折した人必見!HerokuでRailsアプリを公開する方法

heroku toolbaltのインストール

heroku toobaltが最新版であることを確認してください。僕の場合バージョンが古かったのでアップデートしました(Macを使ってます)。

$brew install heroku/brew/heroku

heroku サーバーの作成

$heroku create hogekai

デプロイ

まず自分のgitリポジトリーを最新の状態にアップデートします。

$git add .
$git commit -m 'add token authentication features to Rails API'
$git push origin master

環境変数を設定

これを行わないとデプロイするときにエラーがでます。
環境変数関連のコマンドはこちら [heroku] 環境変数の操作 から拝借しました。

# 環境変数一覧
$ heroku config

# 環境変数名を指定して参照
$ heroku config:get ENV_VAR_NAME

# 追加 (一応addも使えます)
$ heroku config:set ENV_VAR_NAME="value"

# 削除 (一応removeも使えます)
$ heroku config:unset ENV_VAR_NAME

では実際に環境変数を設定します。

$heroku config:set TWITTER_KEY="自分のTWITTER_KEY"
$heroku config:set TWITTER_SECRET="自分のTWITTER_SECRET"

デプロイ

$git push heroku master

トラブルシューティング

remote:        Devise.secret_key was not set. Please add the following to your Devise initializer:
remote:
remote:          config.secret_key = 'シークレットキー'

初期設定ファイルを編集します。
secret_keyは、devise_token_authではなく、devise側で処理するものなので、新しくDevise.setupを付け足しました。別のファイルを作ってもいいです。

initializers/devise_token_auth.rb
DeviseTokenAuth.setup do |config|
...
end

# ここから下をつけたす
Devise.setup do |config|
  config.secret_key = ENV.fetch('DEVISE_SECRET')
end

dotenvで管理します。

// .env
DEVISE_SECRET = 'シークレット'

herokuに環境変数を渡します。

$heroku config:set DEVISE_SECRET="シークレット"

リポジトリを更新します。

$git add .
$git commit -m 'add devise secret'
$git push origin master

もう一度デプロイしてみます。

$git push heroku master

いくつかwarningがでましたがうまく行きました。

スクリーンショット 2018-07-03 2.28.20.png

DBのマイグレーション

$heroku run rake db:migrate

下の様なカラフルなスクリーンが出たら成功です。
スクリーンショット 2018-07-03 2.30.53.png

開く

$heroku open

うまくトップ画面が表示されました!

スクリーンショット 2018-07-03 2.31.57.png

これでAPIも立ち上がっています。

パスワート認証のテスト

試しにポストマンでパスワード認証を行います。
新しいパスを使いましょう。

スクリーンショット 2018-07-03 2.34.36.png

成功です!

スクリーンショット 2018-07-03 2.36.52.png

ついでにパスワードの変更も試します。

スクリーンショット 2018-07-03 2.39.11.png

先ほど受け取ったヘッダーの情報を送ります。
スクリーンショット 2018-07-03 2.39.18.png

これも成功しました。
スクリーンショット 2018-07-03 2.39.32.png

Twitter認証のテスト

Twitterの認証は、ちょっと面倒です。callback urlが違うので開発用のものと本番環境用のアプリを別で登録します。今回は省略しますが、是非自身で挑戦してください。

本番用のKEYとSECRETを取得できたら、heroku側の環境変数を更新して完了です。
ローカルのconfigファイルをいじる方法もあるので、暇な人は調べてください。

まとめ

今回は認証機能をRails APIに実装しました。次回は、Vue.jsの方をがっつりいじって行きます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away