Chapter 04

devise_auth_tokenの実装

arahabica
arahabica
2022.01.17に更新

4.1 LIFFアプリの認証の概要

LIFFアプリでクライアントと自分のサーバ間で安全に認証するにはアクセストークンやIDトークンをサーバに送って認証する必要があります。
今回はLIFFのアクセストークンとRailsのdevise_auth_token Gemを利用することで安全な認証を行います。

4.2 使用ライブラリ

Devise

Railsのデファクトの認証ライブラリ(gem)
認証関連は自作する部分はできるだけ減らして、デファクトを活用

devise_auth_token

Deviseを利用しつつ、APIによる認証を可能にするライブラリ(gem)

4.3 LIFFアプリのセキュリティについて

LIFFを使うと簡単にユーザ情報を取得できますが、そのユーザ情報をサーバに送ってそのまま信頼してしまうと、なりすましやその他の攻撃に対して脆弱になってしまいます。
notsecure.png

↑(LINE公式ページより引用)

安全のためにアクセストークンを使いましょう。

4.4 大まかな処理の流れ

  1. LIFFでアクセストークンを取得
  2. アクセストークンをサーバに送信
  3. サーバでLINEの認証処理
  4. userIdに対応するトークン作成(devise_auth_token)
  5. クライアントからサーバにアクセスする時にトークンをヘッダにつける

これは下記のLINE公式に載っているシーケンス図にのっとった流れになっています。

secure2.png

ただ、「Create new user」の後にdevise_token_authのトークンを生成する処理を追加しています。

4.5 Devise、devise_auth_tokenの導入

まずはdevise_auth_tokenを使える状態に持っていきます。

4.5.1 docker-compose.ymlを一時的に修正

dockerのコンテナを立ち上げたまま、処理をしていきたいので、webサーバのdockerコンテナが落ちないようにします。

docker-compose.yml
-    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
+    #command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
+    command: "tail -f /dev/null"

docker-composeが起動中の場合はCtrl+Cして、再度docker-compose upします。

$ docker-compose up

4.5.2 Deviseとdevise_token_authのインストール

Gemfileに下記のgemを追加します。

Gemfile
gem 'devise'
gem 'devise_token_auth'

gemをインストールします。

$ docker-compose exec web bundle install

4.5.3 Deviseとdevise_token_authの初期化

下記のコマンドでDeviseの設定ファイルやUserテーブルのマイグレーションファイルが生成されます。

$ docker-compose exec web rails g devise:install
$ docker-compose exec web rails g devise_token_auth:install User auth

コンテナ側で生成されたファイルが権限の関係でホスト側で編集できなくなることがあるので、下記コマンドで権限を調整します。

$ docker-compose exec web chown -R "$(id -u $(whoami)):$(id -g $(whoami))" .

マイグレーションを実行してUserテーブルを作成します。

$ docker-compose exec web rails db:migrate

devise_auth_tokenはデフォルトだと、リクエスト毎にtokenが更新されてしまうので、configを修正します。

config/initializers/devise_token_auth.rb
# By default the authorization headers will change after each request. The
# client is responsible for keeping track of the changing tokens. Change
# this to false to prevent the Authorization header from changing after
# each request.
config.change_headers_on_each_request = false # <= コメントアウトを外す

# By default, users will need to re-authenticate after 2 weeks. This setting
# determines how long tokens will remain valid after they are issued.
config.token_lifespan = 2.weeks # <= コメントアウトを外す

# コメントアウトを外す
config.headers_names = {:'access-token' => 'access-token',
                        :'client' => 'client',
                        :'expiry' => 'expiry',
                        :'uid' => 'uid',
                        :'token-type' => 'token-type' }

認証APIのパスを変えたいので、config/routes.rbを少し修正します。

config/routes.rb
Rails.application.routes.draw do
- mount_devise_token_auth_for 'User', at: 'auth'
  root to: 'static#root'
  scope '/api' do
    scope format: 'json' do
+     mount_devise_token_auth_for 'User', at: 'auth'
      resources :stamps, only: [:index, :show]
      resources :imprints, only: [:create]
      delete 'imprints', to: 'imprints#clear'
    end
  end
end

これでdevise_token_authによる認証APIが動く状態になりました。
動作確認をしていきましょう。

4.5.4 認証APIの確認

まず、名前、メールアドレス、パスワードの項目だけのユーザをDBに登録します。

$ docker-compose exec web rails c
> User.create!(name: 'testuser', email: 'example@example.com', password: 'password')

Ctrl+Dでrailsのコンソールを終了します。
そして、テストサーバを起動します。

$ docker-compose exec web rails s -p 3000 -b '0.0.0.0'

Terminalを増やして、以下のcurlコマンドで認証APIを叩きます。

$ curl -D - localhost:3000/api/auth/sign_in -X POST \
  -d '{"email":"example@example.com", "password":"password"}' \
  -H "content-type:application/json"

認証に成功すれば、結果は下記のようになるはずです。(見やすいように少し整形してます。)

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: example@example.com
ETag: W/"12ac3053b26f91ca234280ac13a0790c"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 707fe01b-d25a-4167-b0f2-95e009c9271a
X-Runtime: 0.403161
Vary: Origin
Transfer-Encoding: chunked

{
  "data": {
    "id": 1,
    "email": "example@example.com",
    "provider": "email",
    "uid": "example@example.com",
    "allow_password_change": false,
    "name": "テストユーザー"
  }
}

返ってきたヘッダのaccess-token, client, expiry, uid をリクエストのヘッダにつければ、認証状態でAPIにアクセスすることができます。

4.5.5 トークン検証APIの確認

devise_auth_tokenはトークンの検証用のAPIもあるので試してみましょう。
YOUR_ACCESS_TOKEN, YOUR_CLIENT, YOUR_EXPIRY, YOUR_UID には先ほど取得したものに置き換えてください。

$ curl localhost:3000/api/auth/validate_token -D - \
-H "access-token:YOUR_ACCESS_TOKEN" \
-H "client:YOUR_CLIENT" \
-H "expiry:YOUR_EXPIRY" \
-H "uid:YOUR_UID" \
-H "content-type:application/json"

うまくいけば、下記のような結果になるはずです。

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: test-user+1@gmail.com
ETag: W/"f3e45c8f2942619bd67981aead0bc740"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 0b9e57df-1f3b-4597-9c0f-01a6b3f904be
X-Runtime: 0.086486
Vary: Origin
Transfer-Encoding: chunked

{
  "success": true,
  "data": {
    "id": 1,
    "provider": "email",
    "uid": "example@example.com",
    "allow_password_change":false,
    "name":"テストユーザー"
  }
}

4.5.6 独自APIで認証機能が使えることを確認

独自に作成したAPIでもdevise_token_authの認証機能が使えることを確認しましょう。

まずは、routes.rbに下記のように一行追加してください。

config/routes.rb
Rails.application.routes.draw do
  root to: 'static#root'
  scope '/api' do
    scope format: 'json' do
      mount_devise_token_auth_for 'User', at: 'auth'
      resources :stamps, only: [:index, :show]
      resources :imprints, only: [:create]
      delete 'imprints', to: 'imprints#clear'
+     get '/hello', to: 'hello#index'
    end
  end
end

下記コマンドでHelloコントローラを作成しましょう。

$ docker-compose exec web rails g controller hello
$ docker-compose exec web chown -R "$(id -u $(whoami)):$(id -g $(whoami))" .

コントローラを下記のように入力してください。

app/controller/hello_controller.rb
class HelloController < ApplicationController
  before_action :authenticate_user!
  def index
    message = "こんにちは、#{current_user.name}さん"
    render json: { message: message }, status: :ok
  end
end

before_action :authenticate_user!を冒頭に書くことでこのコントローラへのアクセスは認証を必須にしています。

まずはアクセストークンなしでリクエストしてみます。

$ curl localhost:3000/api/hello -D - \
-H "content-type:application/json"

結果は下記のように401エラーが返ってきます。

HTTP/1.1 401 Unauthorized
...

{"errors":["You need to sign in or sign up before continuing."]}

次にアクセストークンありでリクエストしてみます。

$ curl localhost:3000/api/hello \
-H "access-token:YOUR_ACCESS_TOKEN" \
-H "client:YOUR_CLIENT" \
-H "expiry:YOUR_EXPIRY" \
-H "uid:YOUR_UID" \
-H "content-type:application/json"

下記のようなメッセージが表示されれば成功です。

HTTP/1.1 200 OK
...

{"message":"こんにちは、testuserさん"}

4.6 Next Step

おめでとうございます🎉
これで、devise_token_authを使った認証が使える基盤が整いました。
今のままでは普通のEmail認証になってしまっているので、次章ではこれをLINEに対応させていきます。