外に出るねくら

~ 外に出たって結局やることは自宅と同じ ~

Ruby on RailsのAPIモードでログイン機能の実装をする

概要

タイトルのとおりです。
Rails APIを使ってログイン機能の実装を行います。
今回はサーバーサイドオンリーです。
Google拡張機能でデータをPOSTして、テーブルに格納できていることを確認できればゴールとします。
そのうち、フロントサイドも含めた記事を書く予定ですが、その時の参照先となる記事です。

前提

すでにRailsのプロジェクトがある。

手順概要

実装するための手順の概要です。

やること

  1. devise_token_authのgemのインストール
  2. テーブルの作成・マイグレーション
  3. devise_token_authを使ったトークン認証の実装

devise_token_authのgemのインストール

では早速いきましょう。
まずはGemfileに追加です。

devise_token_authはTwitterFacebookでの認証にも対応することができ、omniauthというGemをインストールすることで使用可能になるのですが、今回はこれらの認証方法は使わないので、omniauthはインストールしません。
devise_token_authを利用する上でインストールすべきGemは devisedevise_token_auth なのですが、
ユーザのデータはJSONでやりとりしたいので jbuilder を、
またクロスドメイン対策のために rack-cors もインストールします。

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.5'
gem 'rake', '~>12.3.2'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'          <- JSONでデータのやりとりを行う想定のためコメントアウトを外す
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'                    <- クロスドメイン対策のために利用するため、コメントアウトを外す

# This is for authentication based on token
gem 'devise'                                <- 追加
gem 'devise_token_auth'                     <- 追加

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Gemfileの編集が終わったらこれらのGemをインストールします。
$ bundle install --path ./vendor/bundle
(プロジェクトごとに管理するgemを変えたいのでローカル(./vendor/bundle)にインストールします。

ここまでエラーなく終了すれば、1st ステップ終了です。

テーブルの作成・マイグレーション

では次はユーザ認証のために使用するクラスや、それに対応するテーブルを作っていきます。

devise_token_authのコントローラ等の作成

$ bundle exec rails g devise_token_auth:install User auth
(bundle exec は必要に応じてつける)

フォーマットは
$ rails g devise_token_auth:install [USER_CLASS] [MOUNT_PATH]
です。
[USER_CLASS] はユーザ認証に使用するクラスの名前です。何も指定しなければUserとなります。
[MOUNT_PATH] は認証のルーティングをマウントするパスです。何も指定しなければauthとなります。
認証のルーティングをマウントとは、要はURLのことで、authだと例えばサインインのURLはGET、POSTともに /auth/sing_in のようになります。

$ bundle exec rails g devise_token_auth:install User auth
      create  config/initializers/devise_token_auth.rb
      create  db/migrate/xxxxxxxxxxxxxx_devise_token_auth_create_users.rb
      create  app/models/user.rb
      insert  app/controllers/application_controller.rb
        gsub  config/routes.rb

となればOK!!

user.rbとマイグレーションファイルの修正

そうすると、 user.rbマイグレーションファイルが作成されるのでそちらを修正していきます。

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  include DeviseTokenAuth::Concerns::User
end

もともとconfirmableやomniauthableがコメントアウトされていましたが、もしコメントアウトされていなければコメントアウトしておきましょう。
confirmableはメールを送信して確認する機能を実装するためのもの、omniauthableは先程のTwitterFacebookでの認証を指しています。
今回は使いません。

次はマイグレーションファイルを修正します。
confirmableがコメントアウトされていなかったので、コメントアウトします。
また、このコメントアウトに伴って confirmation_token に対して張られているindexの記述もコメントアウトします。
今回は、

  • 名前
  • ニックネーム
  • メールアドレス
  • 画像

がデフォルトで記述されていたので、それをそのまま使用します。

class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.1]
  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.text :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

完了したらマイグレーションファイルを適用しましょう。

$bundle exec rake db:migrate
== 20190127155844 DeviseTokenAuthCreateUsers: migrating =======================
-- create_table(:users)
   -> 0.0580s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0552s
-- add_index(:users, [:uid, :provider], {:unique=>true})
   -> 0.0501s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0500s
== 20190127155844 DeviseTokenAuthCreateUsers: migrated (0.2139s) ==============

となっていればOKです!!

ここまででテーブルの作成・マイグレーションが完了しました。

devise_token_authを使ったトークン認証の実装

さぁ、いよいよ実装に入ります。
コードって書いてる時間より紙上とかエクセルとかで考えてる時間のほうが長いですよね。
どうでもいいですけど。

ちなみに今の状態で bundle exec rake routes をすると、

new_user_registration GET /auth/sign_up(.:format) devise_token_auth/registrations#new

のようなパスが表示されています。

コントローラの作成

さて、 bundle exec rails g controller api/v1/auth/registrations を実行します。

$bundle exec rails g controller api/v1/auth/registrations
Running via Spring preloader in process 6670
      create  app/controllers/api/v1/auth/registrations_controller.rb
      invoke  test_unit
      create    test/controllers/api/v1/auth/registrations_controller_test.rb

と表示されればOK。

作成されたコントローラを編集して以下のようにします。

# registrations_controller.rb
module Api
  module V1
    module Auth
      class RegistrationsController < DeviseTokenAuth::RegistrationsController
        private
        def sign_up_params
          params.permit(:name, :nickname, :email, :img, :password, :password_confirmation)
        end

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

ルートの編集

次はルートを編集します。デフォルトのコントローラではなく、DeviseTokenAuth::RegistrationsControllerを継承したコントローラを使うようにします。

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

さて、ルートが確認しましょう。 $ bundle exec rake routes を実行します。

new_api_user_registration GET /api/v1/auth/sign_up(.:format) api/v1/auth/registrations#new

URIがちゃんと変わってますね!
実装としては以上です。

Initializerの編集

もろもろの設定を触ります。

config.change_headers_on_each_request = true にするとリクエストごとにトークンを新しくする必要があります。
なのでコメントアウトを外してfalseにしたいです。
そして、ずっと同じトークンを使い続けるわけにもいかないので config.token_lifespan = 2.weeksコメントアウトも外します。

以上で、サーバー側の実装完了です。
試しに適当にデータを投入してテストしてみましょう。

Restlet Clientを使ってテスト

まずは rails srailsをローカルで起動します。

ユーザ作成
http://localhost:3000/api/v1/auth に対して、POSTメソッドでリクエストを投げます。

Request Body

{
  "name": "kenny takimura",
  "nickname": "kenny",
  "email": "kennytakimura@example.com",
  "password": "password",
  "password_confirmation": "password"
}

f:id:Kenny27:20190129013915p:plain

200ステータスが返ってきてますね。
Response見てみましょう。

Response
**idは人によって違うかもしれないです。
僕はすでにいくつかユーザ登録していたので新規登録でidが4になっています。

{
 "status": "success",
 "data":{
  "id": 4,
  "provider": "email",
  "uid": "kennytakimura@example.com",
  "allow_password_change": false,
  "name": "kenny takimura",
  "nickname": "kenny",
  "image": null,
  "email": "kennytakimura@example.com",
  "created_at": "2019-01-28T16:00:20.000Z",
  "updated_at": "2019-01-28T16:00:21.000Z"
 }
}

テーブルも確認しましょう。
f:id:Kenny27:20190129014447p:plain
ちゃんとデータが入ってますね。

ログイン
ついでにログインも確認しておきましょう。
ログインするためには http://localhost:300/api/v1/auth/sign_in に対してPOSTメソッドでリクエストを投げます。

Request Body

{
  "email": "kennytakimura@example.com",
  "password": "password"
}

f:id:Kenny27:20190129013941p:plain

こちらも200ステータスが返ってきてます。
Responseは

Response

{
 "data":{
  "id": 4,
  "email": "kennytakimura@example.com",
  "provider": "email",
  "uid": "kennytakimura@example.com",
  "allow_password_change": false,
  "name": "kenny takimura",
  "nickname": "kenny",
  "image": null
 }
}

先ほど登録したデータが返ってきます。
idも4です。

いい感じですね。

おわりに

想像してたより簡単に実装できました。
他にもいろんなサイトで実装方法が紹介されているので、見てみるといいかもしれないです。
私は基本的には公式サイト
Installation - devise-token-auth
をベースに、
web帳 | Rails5 + devise token authで作る 認証API

[Rails5] devise_token_auth でAPIを作成する / 新規登録・ログイン | tackeyy.com
を参考に実装していきました。

次はフロントとのつなぎも書いていこうと思います。

それでは!