「MoneyForward Advent Calendar 2015 - Qiitaの14日目」の記事です。
今年、一体何やっていたかなと振り返ってみたところ、nyauthというgemを作っていたことに気づいたので、雑に紹介します。
「お前エモい記事しか書かなくなったな」みたいな声もチラホラ聞くのでたまにはプログラミングの記事を。
こうして振り返ってみると、何故かコントリビューターも現れ始めて嬉しかったです。
何なの
一般ユーザー向けと管理者用向けといった複数のコンテクストに対応した認証gemです。
猫の「にゃん」(って何)と「Authentication」を合わせて「にゃおーす」つまりnyauth
です。
なんで作ったの
個人プロジェクトでとあるWebアプリケーションを作る際に、せっかくだし deviseから卒業しようってのと、単純になるべくメタプロし過ぎないシンプルなものを作りたかったのです。
ただそれだけです。学びが多かった。それが収穫です。
どんなもの
ざっと
- 登録
- 認証
- パスワード変更
- 本人確認(メールが届くかどうか)
- パスワード変更
といった機能があります。一般ユーザーにはこれら全てを提供して、管理者ユーザーには、「認証だけ」みたいなユースケースに対応してます。
一般ユーザーの認証URLは
/session/new
管理者ユーザーの認証URLは
/admin/session/new
と言った感じで、現在のコンテクストをURLのパスで判断してます。
ルーティング
config/routes.rb
Rails.application.routes.draw do mount Nyauth::Engine => "/" end
/
にマウントすると以下の様なroutes
が定義されます。/
にマウントした時にはデフォルトでUser
モデルが認証の対象となります。
Prefix Verb URI Pattern Controller#Action nyauth /nyauth Nyauth::Engine Routes for Nyauth::Engine: registration POST /registration(.:format) nyauth/registrations#create new_registration GET /registration/new(.:format) nyauth/registrations#new session POST /session(.:format) nyauth/sessions#create new_session GET /session/new(.:format) nyauth/sessions#new DELETE /session(.:format) nyauth/sessions#destroy edit_password GET /password/edit(.:format) nyauth/passwords#edit password PATCH /password(.:format) nyauth/passwords#update PUT /password(.:format) nyauth/passwords#update confirmation_requests POST /confirmation_requests(.:format) nyauth/confirmation_requests#create new_confirmation_request GET /confirmation_requests/new(.:format) nyauth/confirmation_requests#new confirmation GET /confirmations/:confirmation_key(.:format) nyauth/confirmations#update reset_password_requests POST /reset_password_requests(.:format) nyauth/reset_password_requests#create new_reset_password_request GET /reset_password_requests/new(.:format) nyauth/reset_password_requests#new edit_reset_password GET /reset_passwords/:reset_password_key/edit(.:format) nyauth/reset_passwords#edit reset_password PATCH /reset_passwords/:reset_password_key(.:format) nyauth/reset_passwords#update PUT /reset_passwords/:reset_password_key(.:format) nyauth/reset_passwords#update
config/routes.rb
Rails.application.routes.draw do mount Nyauth::Engine => "/hoge" end
/hoge
にマウントすれば、Hoge
モデルが認証の対象となります。
複数のモデルを認証したいときのルーティング
config/routes.rb
Rails.application.routes.draw do # for admin namespace :nyauth, path: :admin, as: :admin do # concerns :nyauth_registrable concerns :nyauth_authenticatable # concerns :nyauth_confirmable end # for user mount Nyauth::Engine => "/" end
本当は、2箇所に違う名前でマウントしたいのだけども
Rails.application.routes.draw do # for admin mount Nyauth::Engine => "/admin", as: 'admin' # for user mount Nyauth::Engine => "/", as: 'user' end
とは出来ません。Railsが2箇所にEngineをマウントはできてもURLヘルパーを上手く扱えず後で定義した方のas
で上書いてしまうので、今回はmount
は1つ、2つ目以降は
namespace :nyauth, path: :admin, as: :admin do # concerns :nyauth_registrable concerns :nyauth_authenticatable # concerns :nyauth_confirmable end
と言った感じで定義します。もちろん、1つ目からこの形式でも大丈夫です。
ここで、nyauthでは以下のRouting Conrcenを使えます。
concern :nyauth_registrable do resource :registration, only: %i(new create) end concern :nyauth_authenticatable do resource :session, only: %i(new create destroy) resource :password, only: %i(edit update) resources :reset_password_requests, only: %i(new create) resources :reset_passwords, param: :reset_password_key, only: %i(edit update) end concern :nyauth_confirmable do resources :confirmation_requests, only: %i(new create) get '/confirmations/:confirmation_key' => 'confirmations#update', as: :confirmation end
コントローラー
application_controller.rb
class ApplicationController < ActionController::Base include Nyauth::ControllerConcern before_action -> { require_authentication! as: :user } helper_method :current_user private def current_user current_authenticated(as: :user) end end
使いたいコントローラーに、include Nyauth::ControllerConcern
と書いておくと、require_authentication!
というメソッドが生えるので、
before_action -> { require_authentication! as: :user }
てな感じで、フィルターをかけるイメージです。
管理者向けのコントローラーにも同じように、
admin/base_controller.rb
class Admin::BaseController < ActionController::Base include Nyauth::ControllerConcern before_action -> { require_authentication! as: :admin } helper_method :current_admin private def current_admin current_authenticated(as: :admin) end end
テーブル定義
class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :nickname # Authenticatable t.string :email, null: false t.string :password_digest, null: false t.string :password_salt, null: false t.string :reset_password_key t.datetime :reset_password_key_expired_at # Confirmable t.datetime :confirmed_at t.string :confirmation_key t.datetime :confirmation_key_expired_at t.timestamps null: false end add_index :users, :email, unique: true end end
モデル
適切なカラムを定義した上で、モデルにmodule
をinclude
しておきます。
app/models/user.rb
class User < ActiveRecord::Base include Nyauth::Authenticatable include Nyauth::Confirmable end
app/models/admin.rb
class Admin < ActiveRecord::Base include Nyauth::Authenticatable end
なんとなく見れば分かるかな。更に詳しくは、READMEやソースコードを読んで頂けると良いかと嬉しいです。
学び
- 同じEngineを複数のパスにマウントする事を想定していないようでつらかった。もうちょいソースを読み込んでコントリビュートしたい気持ちはある。
- そのため、helperの生成がつらかった。実装的にもシンプルじゃないのでいけてない。
- テスト時に、
sign_in(user)
みたいなヘルパーを使うために、wardenを大いに参考にさせていただいた。Rack層の段階で次のrequestにhookするという発想がとても勉強になった。 - generator生成のノウハウを得た。
- respondersの拡張ノウハウを得た。
- gemのconfigを用意してゴニョゴニョカスタマイズするノウハウを得た。
個人プロジェクトの進捗どうですか
なんと肝心の個人プロジェクトのWebアプリは、このgemを作ることに寄り道したり、React.jsをこねくり回したり、RailsとJavaScriptのモダンな共存などを模索していたせいで公開がまで来てません。個人プロジェクトは、yak shavingばかりしていた1年でしたね。
今回の記事でnyauthにご興味持たれた方は是非Pull Requestでフィードバック頂けると嬉しいです。