「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
nyauth /nyauth Nyauth::Engine
Routes for Nyauth::Engine:
registration POST /registration(.:format) nyauth/registrations
new_registration GET /registration/new(.:format) nyauth/registrations
session POST /session(.:format) nyauth/sessions
new_session GET /session/new(.:format) nyauth/sessions
DELETE /session(.:format) nyauth/sessions
edit_password GET /password/edit(.:format) nyauth/passwords
password PATCH /password(.:format) nyauth/passwords
PUT /password(.:format) nyauth/passwords
confirmation_requests POST /confirmation_requests(.:format) nyauth/confirmation_requests
new_confirmation_request GET /confirmation_requests/new(.:format) nyauth/confirmation_requests
confirmation GET /confirmations/:confirmation_key(.:format) nyauth/confirmations
reset_password_requests POST /reset_password_requests(.:format) nyauth/reset_password_requests
new_reset_password_request GET /reset_password_requests/new(.:format) nyauth/reset_password_requests
edit_reset_password GET /reset_passwords/:reset_password_key/edit(.:format) nyauth/reset_passwords
reset_password PATCH /reset_passwords/:reset_password_key(.:format) nyauth/reset_passwords
PUT /reset_passwords/:reset_password_key(.:format) nyauth/reset_passwords
config/routes.rb
Rails.application.routes.draw do
mount Nyauth::Engine => "/hoge"
end
/hoge
にマウントすれば、Hoge
モデルが認証の対象となります。
複数のモデルを認証したいときのルーティング
config/routes.rb
Rails.application.routes.draw do
namespace :nyauth, path: :admin, as: :admin do
concerns :nyauth_authenticatable
end
mount Nyauth::Engine => "/"
end
本当は、2箇所に違う名前でマウントしたいのだけども
Rails.application.routes.draw do
mount Nyauth::Engine => "/admin", as: 'admin'
mount Nyauth::Engine => "/", as: 'user'
end
とは出来ません。Railsが2箇所にEngineをマウントはできてもURLヘルパーを上手く扱えず後で定義した方のas
で上書いてしまうので、今回はmount
は1つ、2つ目以降は
namespace :nyauth, path: :admin, as: :admin do
concerns :nyauth_authenticatable
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
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
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年でしたね。
github.com
今回の記事でnyauthにご興味持たれた方は是非Pull Requestでフィードバック頂けると嬉しいです。