RailsアプリではJWTをcookie管理するしかない!!


発端

  • JWTログインを、Railsで実装したかった(Gemはjwt/ruby-jwt を利用)
  • ペイロードを基にしたトークン発行まではできた(記事がたくさん書いてある)
  • RFCを見ると、トークンはリクエストヘッダーのAuthorizationBearerスキーマに入れるのがお決まりらしい
  • じゃあどうやって認証サーバから受け取ったトークンをリクエストヘッダに入れるのかな........ん?
  • 入れ方がどこにも書いてない
  • みんなトークン作った後はなぜかcurlで確認だけして文章終了してる
$ curl example.com -H 'Authorization: Bearer <token>'
  • これをアプリでやりたいんだっちゅうに!!
  • なんで誰もWebアプリでの実装方法書いてないの??
  • (某有名プロジェクトに関わっている人)「RailsじゃAuthorizationヘッダーに入れるの無理だよ
  • ?!?!

結論

  • もうでていますが、Railsのロジック上、受け取ったJWTトークンをAuthorizationヘッダに入れてリクエストを飛ばすといったオシャレなことはできません
  • なので、おとなしくCookieに入れてそこで管理しましょう
  • ただセキュリティ対策として、secure属性httponly属性はつけておきましょう!
# bundle install
gem 'jwt'
# lib/json_web_token.rb(config/application.rbでlib配下をautoloadするようにしてください)
class JsonWebToken
  class << self
    def encode(payload, exp = 24.hours.from_now)
      payload[:exp] = exp.to_i
      JWT.encode(payload, secret)
    end

    def decode(token)
      body = JWT.decode(token, secret)[0]
      HashWithIndifferentAccess.new body
    end

    private

    def secret
      Rails.application.secrets.secret_key_base
    end
  end
end
# ApplicationController内でincludeしてください
module SessionHelper
  def current_user
    if (token = cookies[:jwt])
      decoded = JsonWebToken.decode(token)
      @current_user ||= User.find_by(id: decoded[:id])
    end
  rescue JWT::ExpiredSignature
    flash[:danger] = 'Token Expired'
    log_out
    @current_user = nil
    nil
  end

  def logged_in?
    current_user.present?
  end

  # before_actionでログインしていないと入れないページに設定
  def authenticate_user?
    redirect_to login_path unless logged_in?
  end

  def log_in(user)
    payload = { id: user.id }
    token = JsonWebToken.encode(payload)
    # もしhttps通信なら、secure属性もOnにすること
    cookies.permanent[:jwt] = { value: token, httponly: true }
  end

  def log_out
    cookies.delete(:jwt)
  end
end
  • 「そんなことはない(RailsでもAuthorizationヘッダー使えるよ)」というお方、是非とも反論してください!論破してください!お願いします!!