はじめに
私がJWT認証で発生したちょっとしたエラーを紹介します。
JWTとは
JWT(JSON Web Token)は、認証情報をJSON形式で安全に交換できる方法のことです。
詳しい説明は以下の記事を参考にしてください。
実行内容
Postmanでhttp://127.0.0.1:3000/api/users
にGETメソッドを送り、全ユーザー情報の取得。
※Requestのheaderに以下の認証情報を設定しています。
キー:Authorization 値:Bearer {{jwt token}}
Rails.application.routes.draw do
namespace :api do
resources :users
# ・
# ・
# ・
end
end
module Api
class UsersController < ApplicationController
include JwtAuthenticator
before_action :jwt_authenticate, only: %i[index]
def index
users = User.all.map { |user| user.slice(:id, :email) }
render json: { users: users }
end
# ・
# ・
# ・
end
end
module JwtAuthenticator
require "jwt"
SECRET_KEY_BASE = Rails.application.secrets.secret_key_base
def jwt_authenticate
render json: { error: "認証情報が不足しています。" }, status: :unauthorized if request.headers["Authorization"].blank?
encoded_token = request.headers["Authorization"].split("Bearer ").last
payload = decode(encoded_token)
@user = User.find_by(id: payload[:user_id])
render json: { error: "認証に失敗しました。" }, status: :unauthorized if @user.blank?
@user
end
# 暗号化処理
def encode(user_id)
expires_in = 1.month.from_now.to_i
preload = { user_id: user_id, exp: expires_in }
JWT.encode(preload, SECRET_KEY_BASE, 'HS256')
end
# 復号化処理
def decode(encoded_token)
decoded_dwt = JWT.decode(encoded_token, SECRET_KEY_BASE, true, algorithm: 'HS256')
decoded_dwt.first
end
end
エラー内容
以下のresponseボディが返ってきました。
{
"error": "認証に失敗しました。"
}
原因
データの復号後、payload[:user_id]
のようにシンボルでキーを指定していたことが原因でした。
payload = decode(encoded_token)
@user = User.find_by(id: payload[:user_id])
jwtはJSON形式のデータを暗号化・復号化するため、暗号化する際にデータを一度JSON形式に変換しています。JSON形式は文字列のみで構成されるため、シンボルをキーとしていた場合、文字列のキーに置き換えられてしまいます。暗号化前と復号後のpreload
は以下のとおりです。
暗号化前:preload = { user_id: user_id, exp: expires_in }
復号化後:preload = { "user_id" => user_id, "exp" => expires_in }
解決策
解決策としては、キーを文字列からシンボルに変換させる方法、キーを文字列で指定する方法の2種類あります。
解決策1:キーを文字列からシンボルに変換
transform_keys(&:to_sym)
で文字列からシンボルに変換することが出来ます。
payload = decode(encoded_token).transform_keys(&:to_sym)
@user = User.find_by(id: payload[:user_id])
解決策2:キーを文字列で指定
:user_id
ではなく”user_id”
で指定してあげることで、復号後データを変換しなくてもキーを指定することが可能です。
payload = decode(encoded_token)
@user = User.find_by(id: payload["user_id"])
まとめ
- JWT認証はJSON形式のデータを暗号化していること。
- JSON形式はシンボルで記載出来ないこと。