【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 end
modelファイルも生成されるのでバリデーションを追加
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 } end
has_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とかにもたせるのがよいのかな。
以上です。