はじめに
Ruby on RailsのAPIモードで、モバイルアプリケーションやSPAのためのかんたんなトークンベース認証を実装する例。
なお、タイトルの通りかんたんなものを目指しているので、複雑なことをしたいなら、 devise_token_auth等を使ったほうが早い場合もある。
検証環境
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.1 BuildVersion: 18B75 $ ruby -v ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18] $ rails -v Rails 5.2.1
プロジェクトの作成
$ rails new sample-api --api --database postgresql -T
--api
でAPIモードを指定。--database
はお好きなもの。この記事では、Herokuにデプロイすることも考えてPostgreSQLを指定する。-T
で テスト関連のファイルの生成をスキップする。
初期設定
DBの作成とマイグレーション
$ rails db:create db:migrate
Rack::CORSまわりの初期設定
Gemfile
の29行目あたりのコメントアウトを外す。
# Gemfile # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible gem 'rack-cors'
bundle install
実行
$ bundle install
config/initializers/cors.rb
を編集。コメントアウトされた部分の #
を外し、 origins
に *
を指定する。
Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end
CORS(Cross-Origin Resource Sharing)についてはこの辺を参照。
オリジン間リソース共有 (CORS) - HTTP | MDN
要するに、クロスドメイン制約というセキュリティ上の理由でサーバと通信できるドメインや許可するHTTPヘッダやメソッドを絞る機構があるのだけど、APIサーバでそれをやられると困るので、全部許可しておくという設定をしたということだ。
Userモデル
bcrypt gemのインストール
前準備として、 has_secure_password
を利用するために bcrypt
gemをインストールする。Gemfile
の17行目あたりにある bcrypt
のコメントアウトを外して、 bundle install
を実行する。
# Gemfile # Use ActiveModel has_secure_password gem 'bcrypt', '~> 3.1.7'
$ bundle install
Userモデルの作成
Userモデルを作成する。
$ rails g model User email:string name:string password_digest:string token:string
生成された db/migrate/20181114012824_create_users.rb
を編集する。
- token以外の各カラムにNOT NULL制約を追加
- tokenカラムにはNOT NULL制約を追加しない。*1
email
カラムにUNIQUE制約を追加token
カラムにUNIQUE制約を追加
# db/migrate/20181114012824_create_users.rb class CreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :email, null: false t.string :name, null: false t.string :password_digest, null: false t.string :token t.timestamps end add_index :users, :email, unique: true add_index :users, :token, unique: true end end
app/models/user.rb
を編集。 has_secure_password
と has_secure_token
の追記、その他バリデーション。
# app/models/user.rb class User < ApplicationRecord has_secure_password has_secure_token validates :email, presence: true validates :email, uniqueness: true validates :name, presence: true validates :password_digest, presence: true validates :token, uniqueness: true end
DBのマイグレーションを行う。
$ rails db:migrate
rails consoleで動作確認をする。
user = User.new(name: 'mktakuya', email: 'mktakuya@example.com', password: 'password') user.save #=> true user.token #=> "何らかの文字列" user.authenticate('password') #=> #<User id: 1, name: "mktakuya", ... > user.authenticate('wrong_password') #=> false
認証処理の実装
ヘルパーメソッドの追加
ApplicationController
に認証用のヘルパーメソッドを追加する。
# app/controllers/application_controller.rb class ApplicationController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods before_action :authenticate! private def authenticate! authenticate_or_request_with_http_token do |token, options| User.find_by(token: token).present? end end def current_user @current_user ||= User.find_by(token: request.headers['Authorization'].split[1]) end end
before_action :authenticate!
により、全Controllerの全Actionに認証をかける事ができる。- 認証無しでもアクセスできるようにしたい場合は、後述する
skip_before_action
を利用する。
- 認証無しでもアクセスできるようにしたい場合は、後述する
- Controller内で
current_user
メソッドを呼び出すと、ログイン中のユーザの情報にアクセスすることが出来る。
UsersControllerの実装
UsersController生成
UsersController
を作成する。
$ rails g controller Users create sign_in
config/routes.rb
を編集
# config/routes.rb Rails.application.routes.draw do resources :users, only: [ :create ] do collection do post 'sign_in' end end end
ログイン
ログイン処理は、 users#sign_in
( UsersController
の sign_in
アクションを指す) に記述する。
# app/controllers/users_controller.rb class UsersController < ApplicationController skip_before_action :authenticate!, only: [ :create, :sign_in ] def create end def sign_in @user = User.find_by(email: params[:email]) if @user && @user.authenticate(params[:password]) render json: @user else render json: { errors: ['ログインに失敗しました'] }, status: 401 end end end
- ログイン処理の大まかな流れは、Rails Tutorialで作成した
Sessions#create
と同じ*2。ただし、処理終了後にどこかにリダイレクトしたりviewをレンダリングしたりするのではなく、UserのJSONを返すか、エラーメッセージを返すかである。 skip_before_action
でauthenticate!
を指定するのを忘れないこと。- ログインや新規作成の前にログイン状態で無いのは当たり前である。
新規登録
新規登録処理は、 Users#create
に記述する。
# app/controllers/users_controller.rb class UsersController < ApplicationController skip_before_action :authenticate!, only: [ :create, :sign_in ] def create @user = User.new(email: params[:email], password: params[:password], name: params[:name]) if @user.save render json: @user else render json: { errors: @user.errors.full_messages }, status: 400 end end def sign_in ### 省略 ### end end
動作確認
ログイン中ユーザの情報を返すアクションを作成
動作確認用に、ログイン中ユーザの情報を返すアクション users#me
を UsersController
に作成する。
# app/controllers/users_controller.rb class UsersController < ApplicationController skip_before_action :authenticate!, only: [ :create, :sign_in ] ### 省略 ### def me render json: current_user end end
config/routes.rb
を編集する。
Rails.application.routes.draw do resources :users, only: [ :create ] do collection do post 'sign_in' get 'me' # ← 追加 end end end
Insomniaのインストール
普通のWebサイトならWebブラウザで動作確認すればいいのだが、これはAPIサーバなので、何らかのHTTPクライアントを使う必要がある。curlコマンドで頑張ってもいいけど、せっかくなのでInsomniaというイケてるソフトウェアを使いましょう。
新規登録
新規リクエストを以下の要領で作成する。
- Name:
/users/
- Method:
POST
- Body:
JSON
画面上部のURL欄に http://localhost:3000/users/
をセットし、Body欄に以下のJSONを記述する。
{ "email": "user@example.com", "password": "password", "name": "Example太郎" }
スクショのとおりになっていればOK。
Sendボタンを押してリクエストを送信し、ユーザ登録が完了するとユーザ情報が降ってくる。
token
はともかく、 password_digest
も一緒に降ってくるのはどうなんだという話もあるが、それはまた別の話なので、この記事では省略する。
ログイン
同じように、ログイン処理の新規リクエストを作成する。
- Name:
/users/sign_in
- Medhod:
POST
- Body:
JSON
URL欄は http://localhost:3000/users/sign_in
、Body欄にはログイン情報をJSONで記述。
{ "email": "user@example.com", "password": "password" }
Sendボタンを押してリクエストを送信し、ログインに成功するとユーザ情報が降ってくる。今後、このレスポンスに含まれるtokenを利用してサーバとの通信を行う。
/users/me の動作確認
tokenを利用したサーバとの通信の例として、ログイン中ユーザの情報を取得する /users/me
にリクエストを送信してみる。
新規リクエストを以下の要領で作成する。
- Name:
/users/me
- Method:
GET
tokenはHTTPのヘッダに含めるので、AuthタブからBearerを選択し、tokenを設定する。正しいtokenが設定されていると、ログイン中ユーザの情報がレスポンスとして返ってくる。
おわりに
こんな感じで、かんたんなトークンベース認証の機構を作ることが出来た。
Insomniaでの検証時はrails consoleからトークンをコピペしてセットしていたが、実際にモバイルアプリケーションを作るときは、Emailとパスワードによるログイン後、レスポンスとして返ってきたトークンをiOSのUserDefaultsに保存し以後のリクエスト送信で利用するなどすれば良い。
なお、今回作ったトークンベース認証の機構は、Railsのデフォルトの機能だけを使って実現しているので、非常にシンプルなものとなっている。もっと複雑なこと、例えばログイン元ごとに複数のトークンを管理したいだとか、トークンに有効期限を設けたいとかなったら、 devise_token_auth
gemを使ったりすると良いと思う。