KnockでRails APIモードでJWT認証してみる

More than 1 year has passed since last update.


Rails APIモードでJWT認証したい

Rails APIモードでJWT認証したくて、Gemを探したところ、knockというGemが良さそうでした。

knockの特徴は下記の通りです。


  • 軽い

  • Rails APIモードに対応している


  • Auth0のようなサービスでも利用されている


サンプルアプリケーションを作ってみる

今回はサンプルとして、認証されたユーザーだけがブログの投稿を閲覧できるシステムを作ってみます。


APIモードのアプリケーションを作成する

Rails5のAPIモードのアプリケーションを作成します。

$ bundle init

$ vim Gemfile

GemfileにRails5 gem 'rails', '5.0.0.1' を追記してインストールします。

$ bundle install --path vendor/bundle

Rails APIモードのアプリケーションを作成します。

$ bundle exec rails new sample_knock_api --api

$ cd sample_knock_api


事前準備


model


Post model を用意します

$ bin/rails g model Post title:string body:text --no-test-framework


app/models/post.rb

class Post < ApplicationRecord

validates :body, presence: true
validates :title, presence: true
end


User model を用意します

has_secure_passwordは、Bcryptで暗号化したものをセットしたり認証する機能を提供するメソッドです。これを使うためにpassword_digestカラムを用意します。

$ bin/rails g model user password_digest:string name:string email:string --no-test-framework


app/models/user.rb

class User < ApplicationRecord

has_secure_password

validates :name, presence: true
validates :email, presence: true
end


マイグレーション。

$ bin/rails db:create db:migrate


初期データを突っ込みます

Gemfileにgem 'ffaker'を追加して、bundle install --path vendor/bundleします。

ffakerはテストデータを作成するためのGemです。Postのデータを適当に作成します。


db/seeds.rb

require 'ffaker'

Post.destroy_all
User.destroy_all

User.create!({
name: '田中 太郎',
email: 'test@user.com',
password: 'test123',
password_confirmation: 'test123'
})

10.times do
Post.create!(
title: FFaker::Lorem.sentence,
body: FFaker::Lorem.paragraphs.join(' ')
)
end


初期データを投入します。

$ bin/rails db:seed

データベースに接続してデータが作成されているか確認します。

$ bin/rails db

# テーブル一覧の確認
sqlite> .tables
# テーブル定義の確認
sqlite> .schema users
# データ確認
sqlite> SELECT * FROM posts;
# 終了する
sqlite> .quit


JSON APIサーバーにする

これでModelとサンプルデータの作成が完了したので、次にControllers, Resources, Routingの設定を行っていきます。

$ bin/rails g controller Posts

Gemfileにgem 'jsonapi-resources'してJSONAPI::Resourcesを導入します。JSONAPI::ResourcesはJSON APIサーバーを開発するのに便利な機能を提供してくれるGemです。


app/controllers/posts_controller.rb

class PostsController < ApplicationController

include JSONAPI::ActsAsResourceController
end

Postsリソースを作成します。

$ bin/rails generate jsonapi:resource posts


app/resources/post_resource.rb

class PostResource < JSONAPI::Resource

immutable
attributes :title, :body
end

routingの設定。


config/routes.rb

...

jsonapi_resources :posts
...


認証を行う

さて、ここからがJWT認証を行っていきます。

Gemfileにgem 'knock'を追記してbundle installします。

# knockをインストールする

$ bin/rails generate knock:install

create config/initializers/knock.rb

# userがサインインできるようにする。外部サービスを使用する場合はここは不要
$ bin/rails generate knock:token_controller user

create app/controllers/user_token_controller.rb
route post 'user_token' => 'user_token#create'

application_controllerKnock::Authenticableモジュールを追加します。


app/controlers/application_controller.rb

class ApplicationController < ActionController::API

include Knock::Authenticable
end

posts_contorllerbefore_action :authenticate_userを追加することで、Postリソースを保護します。


app/controllers/posts_controller.rb

class PostsController < ApplicationController

include JSONAPI::ActsAsResourceController
before_action :authenticate_user # この一行を追加
end

さて、これで完成したので試してみます。

まずユーザーの認証を行います。ユーザーのemailとpasswordが正しいとトークンが返ってくるので、そのトークンを用いてpostデータをリクエストすると、postデータが返ってきます。

# サーバーを起動

$ bin/rails s

# 別ターミナルから行う
# ユーザーの認証を行う
$ curl -X "POST" "http://localhost:3000/user_token" -H "Content-Type: application/json" -d $'{"auth": {"email": "test@user.com", "password": "test123"}}'

{"jwt":"eyJ0eXAiO..."}

# postデータをリクエストする
$ curl -X "GET" "http://localhost:3000/posts" -H "Authorization: Bearer eyJ0eXAiO..." -H "Content-Type: application/json"

{"data":[{"id":"1","type":"posts","links":{"self":"http://localhost:3000/posts/1"},"attributes":{"title":"Quos sed ...","body":"Eligendi porro ..."}},{"id":"2","type":"posts","links":{"self":"http://localhost:3000/posts/2"},"attributes":{"title":"Nisi autem ...","body":"Minima quod ..."}}, ...


サンプルコード

sample_knock_apiに今回のサンプルコードを載せております。


参考