Ruby
Rails
WebAPI
認証

Rails5 APIで認証付きのWebAPIを作ってみる

はじめに

Railsもバージョン5になってから、Web APIを簡単に作れるような仕組みが色々増えました。
認証周りの仕組みもRailsは、手軽にできるようになっているみたいなので試してみます。

使用ソフトウェアのバージョン情報

ruby 2.4.1p111 (2.3でも動作確認しました)
Rails 5.1.3 (5.0.1でも動作確認しました)
記事の中でcurlを使ってますが、RESTクライアントって種類のプラグインがChromeなどではいくつか公開されているので、そちらを使ってみてもいいかもしれません。

準備(アプリケーションの土台を作る)

まずはアプリケーションの土台を作ります。主に作るのは2つのリソース(MemoとUser)です。
Memoが認証で守りたいリソースで、Userはログイン情報とかを持っているリソースという設定です。

下記のコマンドを順番に実行してください。

$ rails new railTokenAuth --api
$ cd railTokenAuth
$ rails g scaffold Memo title:string content:string 
$ rails g scaffold User name:string pwd:string token:token
$ rake db:migrate

少し解説すると、ここでUserリソースでtoken型のtokenという名前の属性を与えていますが、これによって
app/models/user.rbが下記のように記述されます。

app/models/user.rb
class User < ApplicationRecord
  has_secure_token
end

このhas_secure_tokenという記述を入れることで、Userモデルにデータが追加されたときに、自動的に
Userモデルが持つtokenという属性にセキュアトークンが生成され保存されます。

初期データの投入

まず別のターミナルでrailsサーバーを立ち上げます。以後これはずっと立ち上げっぱなしです。

$ rails server

次にcurlコマンドを使って、POSTメソッドでMemoリソースとUserリソースにデータを投入します。

$ curl -X POST  -H 'Content-Type:application/json' -d '{ "name": "testuser", "pwd": "ddddd" }' http://0.0.0.0:3000/users

このコマンドの結果は

{"id":1,"name":"testuser","pwd":"ddddd","token":"rzBHzJi3RuftqmFwhevESoPg","created_at":"2017-08-04T05:07:57.661Z","updated_at":"2017-08-04T05:07:57.661Z"}

token属性になにやら値が入ってますね。

$ curl -X POST -H 'Content-Type:application/json' -d '{ "title": "HELLO!", "content": "NAIYOwaNAIYO" }' http://0.0.0.0:3000/memos 

こちらのコマンドの結果はこうなります。

{"id":1,"title":"HELLO!","content":"NAIYOwaNAIYO","created_at":"2017-08-04T05:10:45.410Z","updated_at":"2017-08-04T05:10:45.410Z"}

これでデータの投入が終わりました。
railsコンソールでデータがちゃんと入っているかを確認してみてもいいでしょう。

$ rails console
irb(main):001:0> Memo.all
Memo Load (1.0ms)  SELECT "memos".* FROM "memos"
=> #<ActiveRecord::Relation [#<Memo id: 1, title: "HELLO!", content: "NAIYOwaNAIYO", created_at: "2017-08-04 05:10:45", updated_at: "2017-08-04 05:10:45">]>

irb(main):002:0> User.all
User Load (0.2ms)  SELECT "users".* FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "testuser", pwd: "ddddd", token: "rzBHzJi3RuftqmFwhevESoPg", created_at: "2017-08-04 05:07:57", updated_at: "2017-08-04 05:07:57">]>

ちゃんと入っていますね。

ログイン機能を作る

ログイン機能といっても、画面を作るわけではないので、超簡単に作ります。

$ rails g controller login login

これでloginコントローラーのloginアクションメソッドが出来上がりです。
あとはルート設定とコントローラーの中身をちょこっと作ります。

config/routes.rb
Rails.application.routes.draw do
  resources :users
  resources :memos
  post 'login/login'  # ←これを追記しました
end
app/controllers/login_controller.rb
class LoginController < ApplicationController
  def login
    # ↓ここから
    login_user = User.find_by(name:params[:name],pwd:params[:pwd])
    if login_user != nil
      render plain: login_user.token
    else
      render plain: 'no auth'
    end
    # ↑ここまで追記しました
  end
end

すこし迷いましたがログインはPOSTメソッドで行うようにしました。
送られたユーザー名とパスワードに合致するデータがあれば、ログインOK!としてトークンを返しています。
該当するデータがない場合は"no auth"とかってエラーメッセージ(のつもり)の文字列を返しています。
本当はちゃんとステータス(403とか)を返したほうがいいと思います。。。

ちょっとテストしてみます。

$ curl -X POST -H 'Content-Type:application/json' -d '{ "name": "testuser", "pwd": "ddddd" }' http://0.0.0.0:3000/login/login

結果は

rzBHzJi3RuftqmFwhevESoPg

ちゃんとトークンらしい文字列が返ってきています。

ログイン失敗の場合のテストもやっておきましょう。

$ curl -X POST -H 'Content-Type:application/json' -d '{ "name": "testuser", "pwd":"piyopiyo" }' http://0.0.0.0:3000/login/login
no auth

うまく行ってますね。

認証の仕組みを入れる

最後にMemoリソースのコントローラーに認証の仕組みを入れます。
下記のようにmemos_controller.rbの最初と最後に下記のような記述を足します。

app/controllers/memos_controller.rb
class MemosController < ApplicationController
  include ActionController::HttpAuthentication::Token::ControllerMethods

  before_action :authenticate

  # 中略

  def authenticate
        authenticate_or_request_with_http_token do |token,options|
          auth_user = User.find_by(token: token)
          auth_user != nil ? true : false
        end
  end
end

試してみましょう。

$ curl -X GET -H 'Authorization: Token rzBHzJi3RuftqmFwhevESoPg' -H 'Content-Type:application/json' http://0.0.0.0:3000/memos/1

'Authorization: Token rzBHzJi3RuftqmFwhevESoPg' の中のトークンはさっきログインした時にもらったトークンをそのまま貼り付けてください。
結果は次のようになります。ちゃんとデータが返って来ていますね。

{
  "id": 1,
  "title": "HELLO!",
  "content": "NAIYOwaNAIYO",
  "created_at": "2017-08-04T05:10:45.410Z",
  "updated_at": "2017-08-04T05:10:45.410Z"
}

だめな場合も見ておきます。

curl -X GET -H 'Authorization: Token piyopiyo' -H 'Content-Type:application/json' http://0.0.0.0:3000/memos/1
HTTP Token: Access denied.

ちゃんと拒否されていますね。
これで完成です。

あと残る問題はこのトークンをどうやってクライアントで保持させるかですね。
Web Storageで持っておくものなのかな? (どなたか教えてくださると嬉しいです)

Node.jsで作ってみる

@oz4youさんが、Node.jsで動作するテスト用の認証付きWebAPI(JSON Server)を作成してくれました。
http://qiita.com/oz4you/items/3f3223048bd2109de47b