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

# はじめに

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

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

# 準備（アプリケーションの土台を作る）

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

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

```console
$ 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が下記のように記述されます。

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

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


# 初期データの投入

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

```console
$ rails server
```

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

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

このコマンドの結果は

```json
{"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属性になにやら値が入ってますね。

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

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

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

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

```console
$ 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">]>
```

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


# ログイン機能を作る 

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

```console
$ rails g controller login login
```

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

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :users
  resources :memos
  post 'login/login'  # ←これを追記しました
end
```

```ruby: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とか）を返したほうがいいと思います。。。

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

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

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

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

```console
$ 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の最初と最後に下記のような記述を足します。

```ruby: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
```

試してみましょう。

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

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

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

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

```console
$ 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