Help us understand the problem. What is going on with this article?

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

More than 3 years have 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に今回のサンプルコードを載せております。

参考

kiyodori
なりたてエンジニア
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした