SPAサイトでの認証認可 JWT✗Rails5✗Nuxt.js
2019.03.24
この記事は最終更新日から1年以上が経過しています。
どもです。
SPAでサービスをガンガン作成されているでしょうか?
SPAの認証認可のアクセストークンとして、「JWT(JSON Web Tokens)」を用いる方も多いのではないでしょうか?
前回、「Nuxt.js と auth-module (@nuxtjs/auth)で、JWT(JSON Web Tokens)& OAuth 認証 ログイン」の記事にて、「auth-module (@nuxtjs/auth)」を用いた「JWT(JSON Web Tokens)& OAuth 認証 ログイン」について記事を書いたのですが、今回サーバーも作成してみました。
とにかく試したい方はこちらから
Github
・サーバー側(Rails5)
https://github.com/webcyou-org/jwt-server
・フロント側(Nuxt.js)
https://github.com/webcyou-org/jwt-front
JWT(JSON Web Tokens)を用いて認証認可(ログイン周り)の実装となります。
フロント側は、前回同様「auth-module (@nuxtjs/auth)」のサンプルを使用する際に、フロント側の実装とサーバー側の実装となります。
Railsのpublicフォルダに、Nuxt.jsのSPAモードでリソースをビルドしたものになります。
Rails側で、ログインしたり、ログイン後ユーザー情報を参照したりすることができます。
前回、フロント側の実装に関してだったので、今回はサーバー側の実装に関して書いていこうかなと。
フロント側のポイントは、process.env.NODE_ENVの「development」(Rails APIサーバーをポート3000で動かしている場合)は、config.proxyを設定し、Nuxt.jsはポート3333で立ち上げ、「/api」は「http://localhost:3000」に向けているところですかね。
あと、SPAの認証認可で、cookieを用いてしまうと脆弱性が出てしまうので、「Local Strage」に変更しているところと、endpointsを「/api/v1/」に変更し、「propertyName」をRailsサーバー側から受け取る(knockデフォルト)「propertyName」に変更したところですかね。
では、サーバー側をRails5 APIモードでサクッと作ったので軽く説明でもと。
SPAを実装しようとしている方の何か参考になればと思います。
(Railsについての説明とかは割愛しております。)
Rails5 APIサーバー (Gem Knockを用いたJWT認証)
それでは、手順を書いて行きます。
まず、以下のコマンドで、Rails APIモードのwebアプリを生成。
$ bundle exec rails new jwt-server --api $ cd jwt-server
User Modelを Rails ジェネレートコマンドを用いて生成します。
パスワードはBcryptで暗号化したものをセットするため、has_secure_passwordメソッドを用いて生成し、それを保存するカラム「password_digest」を用意しておきます。
$ bin/rails g modeluser password_digest:string name:string email:string--no-test-framework
app/models/user.rbを以下のように修正。
app/models/user.rb
class User < ApplicationRecord has_secure_password validates :name, presence: true validates :email, presence: true end
seedファイルを修正します。
db/seeds.rb
User.destroy_all User.create!({ name: 'daisuke.takayama', email: 'test@user.com', password: 'test123', password_confirmation: 'test123' })
ダミーデータとして、Userを一人登録します。
emailと、パスワードは上記の様に設定しておきます。
準備ができましたので、データベースの作成とマイグレードを実行。
seedコマンドで、データを挿入しておきます。
$ bin/rails db:create db:migrate $ bin/rails db:seed
といった作業が、この辺のコミットになります。
Userコントローラもジェネレートコマンドで生成しておきます。
$ bin/rails g controller Api/V1/Users
classで生成されたので、moduleに変更しました。
その辺りのコミットとなります。
Gem Knockを追加
それでは、JWT認証 Gemパッケージである「knock」を追加していきます。
Gemfileに「knock」を追加。
bundle インストール
$ bundle install --path vendor/bundle
knockをインストールします。
$ bin/rails generate knock:install
以下が表示すれば成功。
create config/initializers/knock.rb
userがアクセストークンを発行できるように「token_controller」をknockコマンドを用いて生成します。
$ bin/rails generate knock:token_controller user
コントローラとルーティングが生成。
create app/controllers/user_token_controller.rb route post 'user_token' => 'user_token#create'
app/controllers/user_token_controller.rb
class UserTokenController < Knock::AuthTokenController end
このような感じで「user_token_controller.rb」が生成されるのですが、「Api/V1/」にしたかったので、手動で持っていきました。(コマンドで生成したかったのですが、ちょっとやり方わからなかったです)
app/controllers/api/v1/user_token_controller.rb
module Api module V1 class UserTokenController < Knock::AuthTokenController end end end
ルーティングも以下の様に修正。
Rails.application.routes.draw do namespace :api do namespace :v1 do post 'user_token' => 'user_token#create' end end end
「controllers/application_controller.rb」に、「include Knock::Authenticable」を追記し、application_controllerに、Knock::Authenticableモジュールを追加します。
class ApplicationController < ActionController::API include Knock::Authenticable end
と、この辺りのコミットはここらへんとなります。
Users Controller修正
app/controllers/api/v1/users_controller.rb を修正していきます。
before_action :authenticate_user, except: [:create, :logout]
を追加し、Userリソースを保護します。
createメソッドとlogoutメソッドは、exceptで 対象外としております。
createメソッドは新規ユーザー作成ですが、こちらの説明は割愛させていただきます。
indexメソッドで、ログインしているユーザーを返却します。
def index render json: {status: 200, user: response_fields(current_user.to_json) } end
また、ログアウトに関しては、JWTではサーバー側にログインしている状態などを保持しない設計なので、サクセスだけ返却するようにしております。
このまでのコミットが、この辺りとなります。
Usersコントローラ修正に伴い、routesも修正しておきます。
config/routes.rb
get 'users' => 'users#index' post 'users' => 'users#create' delete 'users' => 'users#logout
これまでのコミットが、この辺りとなります。
curlでの検証
と、ここまで実装できれば検証が行える。
はずだったのですが、何度行っても 401エラーとなってしまう。。。
調べたところ、Rails5.2以降で、どうやら「Rails.application.secrets.secret_key_base」は、nilを返すようで、それが影響しているぽい。
回避策として「skip_before_action :verify_authenticity_token」を「UserTokenController」に追加。
module Api module V1 class UserTokenController < Knock::AuthTokenController skip_before_action :verify_authenticity_token end end end
knockのイシュー
https://github.com/nsarno/knock/issues/208
これまでのコミットが、この辺りとなります。
curl Test
アクセストークンの生成(ユーザーログイン時)
$ curl -X "POST" "http://localhost:3000/api/v1/user_token" -H "Content-Type: application/json" -d $'{"auth": {"email": "test@user.com", "password": "test123"}}'
レスポンスは、jwtが返却されます。
{"jwt":"eyJ0eXAiOiJKV1QiLCJhbG...."}
上記のJWTを用いて、ログインユーザー情報を取得。
$ curl -X "GET" "http://localhost:3000/api/v1/users" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbG...." -H "Content-Type: application/json"
ログインしているユーザー情報が返却されるのが確認できました。
{"status":200,"user":{"id":1,"name":"daisuke.takayama","email":"test@user.com"}}
と、今回のサーバー側のソースはこちらになります。
https://github.com/webcyou-org/jwt-server
Github
・サーバー側(Rails5)
https://github.com/webcyou-org/jwt-server
・フロント側(Nuxt.js)
https://github.com/webcyou-org/jwt-front
フロントエンドのassetsは「frontend」ディレクトリで submodule化しております。
終わりに
といった訳で、今回はJWTのサーバー側の実装でした。
時間ができたら、Rails以外にも、SpringBootとか、Djangoとかでも実装してみようかと思っております。
(最初は、SpringBootでやろうかと思いましたが、環境構築がRailsの方が楽だったので。。)
また、同じく時間ができたら、フロント側のフレームワークも、Nuxt.js以外のSPAフレームワーク用いてやってみようかと。
SPAでのJWT認証認可を作成しようとしている方の何かの参考にでもなればと思っております。
ではではぁ。
またまたぁ。