【rails】authenticate_with_http_token を使ってapiの認証機能を実装する
はじめに
今回やりたかったことはアプリからのリクエストを受け付けるapiの開発。やりたいことと流れは以下のようなイメージ
・Emailとパスワードでログインを行う
・ログインに成功したらトークンと有効期限を発行してクライアントに返す
・クライアントは毎リクエストトークンをhttpヘッダーに入れてリクエストする
また、今回はrails5のapiモードを使用しました。
テーブル設計とか準備
・users
| id | ユーザーid |
|---|---|
| メール | |
| password_digest | パスワード |
| token | トークン |
| token_expire | トークン有効期限 |
password_digestはrailsのhas_secure_passwordというのを使う場合にこのカラムが必要なようなので追加。has_secure_passwordはパスワード認証の仕組みを簡単に実装できる仕組みみたい。
とりあえずモデルとテーブルを作成する
$ ./bin/rails g model User
migrationファイルが生成されるのでカラムを追記する
db/migrate/20171009182615_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :email
t.string :password_digest
t.string :token
t.datetime :token_expire
t.timestamps
end
end
endmodelファイルも生成されるのでバリデーションを追加
app/models/user.rb
class User < ApplicationRecord
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
endhas_secure_passwordを使うためにbcryptというのが必要なのでGemfileに追記
Gemfile
gem 'bcrypt', '~> 3.1.7'
ここまでできたら以下を実行
$ ./bin/bundle install $ ./bin/rails db:migrate
実装
あとはプログラムを実装していく。
config/routes.rb
Rails.application.routes.draw do post '/users/signup', to:'users#signup' post '/users/signin', to:'users#signin' end
app/controller/users_controller.rb
# coding: utf-8
class UsersController < ApplicationController
## authenticate_with_http_tokenを使うために
include ActionController::HttpAuthentication::Token::ControllerMethods
## signinとsingup以外で認証処理を行う
before_action :auth, except: [:signin, :signup]
## サインアップ
def signup
user = User.new(user_params)
if user.save
success
else
failed(user.errors.full_messages)
end
end
## サインイン
def signin
## 認証に成功したらランダム文字列でトークンを生成して有効期限1日をセット
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
user.token = create_token
user.token_expire = DateTime.now + 1
if user.save!(:validate => false)
success({ token: user.token })
else
error
end
else
error('email または passwordが正しくありません')
end
end
## レスポンス返却用の簡易メソッド
def success(data = nil)
render json:{ status: 'success', result: data }
end
def error(messages = nil)
render json:{ status: 'failed', messages: messages }
end
## 以下 authenticate_with_http_token を使った処理
def auth
## 認証に失敗したらエラーレスポンスを返す
authenticate_token || render_unauthorized
end
## httpヘッダーに入っているtokenでdbを検索して有効期限のチェック
## authenticate_with_http_tokenの書き方でこのように書くみたい
def authenticate_token
authenticate_with_http_token do |token, options|
user = User.find_by(token: token)
user && DateTime.now <= user.token_expire
end
end
def render_unauthorized
render json:{ result:'failed' }
end
## ランダム文字列を作る
def create_token
return ((0..9).to_a + ("a".."z").to_a + ("A".."Z").to_a).sample(50).join
end
def user_params
params.permit(:email, :password)
end
end
確認はこんな感じで
$ curl -H 'Authorization: Token xxxxxxxxxxxxxxxxxxxxxxxxxx' http://localhost:3000/users
今回はトークンをDBに入れているけど、少し規模の大きいサービスだったらredisとかにもたせるのがよいのかな。
以上です。