すでにArchivedされているgemですが
nsarno/knock: Seamless JWT authentication for Rails API
を読み解いてみます
GitHub repositoryをgit cloneして手元の好きなエディタでコードを読んでもいいですが、お気軽に
https://vscode.dev/github/nsarno/knock/
にアクセスして読んでみます
GitHubにあれば、https://vscode.dev/github/(username)/(repository)でブラウザ上のVSCodeで開いてコードを読むことができ便利
knock gemは、ログイン時にjwtを発行してログイン管理するgemです
まずはREADME.mdを読む
ログインに使うモデル(Userモデルとか)に has_secure_password が必要
ActiveModel::SecurePassword::ClassMethods has_secure_password
一番親のApplicationControllerに include Knock::Authenticable を追加
class ApplicationController < ActionController::API
include Knock::Authenticable
endそして使うコントローラーに before_action :authenticate_user を追加
class SecuredController < ApplicationController
before_action :authenticate_user
def index
# etc...
end
# etc...
endこれでログインしてないとアクセスできないようになります
ログインします
https://github.com/nsarno/knock/blob/master/app/controllers/knock/auth_token_controller.rb の create に email と password を送信すると、jwtが返ってきます
実際に使うにはコントローラーを用意します
ここでは UserTokenController#create を用意
# routes.rb
post 'user_token' => 'user_token#create'user_token_controller.rb を用意
Knock::AuthTokenController を継承
class UserTokenController < Knock::AuthTokenController
endPOST /user_token に email と password を送信すると
{
"jwt": "eyJ0xxxxxxxxxxxxxx"
}といったjwtが返ってきます
そしてアクセスしたいapiのheaderに
Authorization: Bearer eyJ0xxxxxxxxxxxxxx と先ほど取得したjwtをつけて送信するとアクセスできるようになります
knock gemのデフォルトの使い方でコードを読んでいきます
ログイン管理に使うモデルは User
class AuthTokenController > create を読み進めていきます
POST /user_token
{"auth": {"email": "[email protected]", "password": "secret"}}といったアクセスをすると
before_action :authenticate が動いて
User.find_by(email: auth_params[:email]) でユーザーを探し
user.authenticate(auth_params[:password]) でパスワードが正しいか確認
正しい email と password じゃない場合
Knock.not_found_exception_class の例外をなげて
https://github.com/nsarno/knock/blob/master/app/controllers/knock/application_controller.rb でその例外をキャッチ
head :not_found を返す
正しい email と password の場合
render json: AuthToken.new payload: { sub: user.id } でjwtを生成して返す
AuthToken は
https://github.com/nsarno/knock/blob/master/app/model/knock/auth_token.rb の model で jwt を組み立ててます
ログインしてないと使えないAPIには before_action :authenticate_user を設置
そのAPI Headerに Authorization: Bearer eyJ0xxxxxxxxxxxxxx を付与してアクセスします
authenticate_user はどこにも定義されていません
定義されてないメソッドが呼ばれたときは method_missing が呼ばれます
method_missing が Knock::Authenticable で定義されています
https://github.com/nsarno/knock/blob/8e8b3e8d29eccb83a83ea2449e006250f025632a/lib/knock/authenticable.rb#L23
authenticate_user は、current_user のメソッドを呼び、
API Headerの Authorization: Bearer eyJ0xxxxxxxxxxxxxx をdecode、jwtの中身を取り出して
User.find_by(id: payload['sub']) でユーザーを探し、current_user として返します
Knock::AuthToken.new で生成したりデコードしたりしてます
https://github.com/nsarno/knock/blob/8e8b3e8d29eccb83a83ea2449e006250f025632a/app/model/knock/auth_token.rb#L9
Knock::AuthToken.new(payload: payload) でjwtを生成
payloadの中身 exp や aud をセットしてます
exp はデフォルトでは1日です
Knock::AuthToken.new(token: token) でjwtをdecode
デコードのアルゴリズムが違ったり exp が過ぎていたら jwt gemのdecodeで例外を投げます
knock gemを使うのをやめる案件があったのでgemの中身を読み解いてみました
Userモデルを変えられたり、authenticate_user や current_user のメソッド名を変えられたりするので
メタプログラミングが使われてるところは読みづらいですが、小さめのgemでした
PR
Rubyを深堀するの読んでみたい本です
このリンクは、アフィリエイトリンクです