Edited at

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