Nuxt.jsのauth moduleとrails APIモードを使用したJWT認証の実装方法について①

今回はNuxt.jsとrails APIモードを使ったJWT認証(JSON Web Tokens)の実装についてです。JWT実装はサーバーサイド(rails)とクライアントサイド(Nuxt.jsの)二つのパートに分けて書いていきます。今回はサーバーサイドの実装を確認していきましょう!

JWT認証の流れ

JWT認証の流れは以下の図のようになります。

  1. ユーザーがメールアドレスとパスワードを入力
  2. サーバーサイド側でメールアドレスが存在するかDBを検索、signupの場合新しいレコードを作成。
  3. JWTトークンを生成しクライアント側に渡す。
  4. ユーザーが認証が必要なリソースが必要な時トークンを検証

Jwt認証に必要なGemファイルをインストール

今回、bcryptjwtのgemファイルを用意します。bcryptはDBに保存するパスワードを暗号化するためのファイル。jwtはjwt認証に必要なファイルになります。

gem 'bcrypt', '~> 3.1.7'

gem 'jwt'

Gemファイルに記述したらrails bundle installをコマンドで実行します。

UserとAuthコントローラーを作成

続いて、UserとAuthコントローラーを作成します。コマンドで以下を実行します。Userコントローラーはユーザーのサインアップ用に使います。

rails g controller Users create
rails g controller Auth login auto_login 

routesの確認

今回、APIとして使用するので、名前空間を作成しておくことで、今後のAPIのバージョン管理が用意になります。resource :users, only: [:create]はサインアップ用で今回はcreateのみ使用します。post “/login”, to: “auth#login”はログイン用です。get “/auto_login”, to: “auth#auto_login”はユーザーが保持しているトークンが有効か確認する時に必要になります。

Rails.application.routes.draw do
  namespace 'api' do
    namespace 'v1' do
     
      resource :users, only: [:create]
      post "/login", to: "auth#login"
      get "/auto_login", to: "auth#auto_login"
    end
  end
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

サインアップ用のUserコントローラー

サインアップ用のコードをUserコントローラーに記述していきます。ユーザーがサインアップすると新しいユーザーインスタンスが作成されます。クライアントサイドからPOSTリクエストが行われ、ユーザーインスタンスが有効だった場合、ユーザーidを使用してトークンを生成してクライアント側に渡します。

User Controller

module Api
  module V1 
    class UsersController < ApplicationController
      skip_before_action :require_login, only: [:create]
      def create
        user = User.create(user_params)
        if user.valid?
          payload = {user_id: user.id}
          token = encode_token(payload)
          render json: {user: user.user_id, jwt: token}
        else
          render json: {errors: user.errors.full_messages},status: :not_acceptable
        end
      end

      private

      def user_params
        params.permit(:email,:password)
      end
    end
  end
end

Application Controllerで401エラーを定義

401エラーは何度か使うので、Application Controllerで定義して共通化してあげます。

Application Controller
      
      # 401 Unauthorized
      def response_unauthorized
        render status: 401, json: { status: 401, message: 'Unauthorized' }
      end
      

続いてAuthコントローラーです。まずloginメソッドを作成していきます。クライアント側から受け取ったメールアドレスと一致するものがあるかfind_byを使ってDBのレコードを検索します。一致するメールアドレスが存在する場合、authenticateメソッドを使ってパスワードが一致するか確認します。パスワードが有効だった場合、encode_tokenメソッドを使ってトークンを生成し、クライアントに渡します。

Auth Controller

module Api
  module V1
    class AuthController < ApplicationController
      skip_before_action :require_login, only: [:login, :auto_login]
      def login
        user = User.find_by(email: params[:email])
        if user && user.authenticate(params[:password])
          payload = {user_id: user.id,email: user.email}
          token = encode_token(payload)
          render json: {jwt: token,success: "Welcome back, #{user.email}"}
        else
          response_unauthorized
        end
      end

 
    end
  end
end

JWTトークンに必要なメソッドを用意

encode_tokenメソッドはApplication Controllerで定義してあげます。encodeメソッドは最初にインストールしたgemファイル’jwt’に含まれており、JWT認証に必要なtokenを生成します。第一引数にはユーザーidが含まれているpayload、第二引数には秘密鍵。第三引数はアルゴリズムを指定してあげます。

Application Controller

def encode_token(payload)
     JWT.encode(payload,'my_secret_key','HS256')
end

そうすると以下のようなtokenが生成されます。

"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.RbmQ6qKdj8y6GeTBX0EGbLst3wrHuGrNGGOuT5pRPls"

JWTトークンの検証

続いて、ログインしたユーザーが保持しているトークンが適切かどうか検証するコードを記述していきます。Authcontrollerにauto_loginメソッドを追加します。クライアントから送られてきたトークンを検証していきます。

Auth Controller

def auto_login
        if session_user 
          render json: session_user
        else
          render json: {errors: "No user Logged In"}
        end
      end

applicationコントローラーにsession_userメソッドを定義してあげます。decoded_tokenの値が存在する場合、user_idを取り出してDBで検索します。一致するuser_idが存在していたら、auto_loginメソッドに返してあげてクライアント側にデータを渡します。

def session_user
        decoded_hash = decoded_token
        if !decoded_hash.empty?
            user_id = decoded_hash[0]['user_id']
            @user = User.find_by(id: user_id)
        else 
            nil
        end
    end

session_userメソッドでトークンを検証するためにapplication controllerにdecoded_tokenとauth_headerを定義します。トークンはheaderのAuthorizationにAuthorization: bearer <token>で含まれているので、トークンの部分だけ取り出しで検証をします。暗号化作業の時と同じで第一引数にトークン第二引数に秘密鍵、第三引数はトークンの検証を行うかなのでTrueにします。第四引数はアルゴリズムの種類を指定します。

def auth_header
        request.headers['Authorization']
    end 

    def decoded_token
        if auth_header
            token = auth_header.split(' ')[1]
            begin
                JWT.decode(token, 'my_secret', true,algorithm: 'HS256')
            rescue JWT::DecodeError
                []
            end
        end
    end

次回はクライアント側の処理について説明していきます。

>文系エンジニア大学生の技術ブログ

文系エンジニア大学生の技術ブログ

社会が多様化していく中、大学生の学生生活も多様であるべきと考えています。主にエンジニア向けにITやプログラミングなどの技術系と大学生向けに休学、留学、海外生活、トビタテ留学、長期インターンに関する記事を書いています。