今回やりたいこと
Deviseを使った基本的なユーザー認証機能
SNS認証には仮登録メールを介さずにワンクリック登録となるようにしたい
Deviseの初期設定でユーザー情報の編集をする際に逐一パスワードを求められるが、ユーザーフレンドリーではないのでパスワードを入力せずにユーザー情報を編集したい
この記事の個人的目的
備忘録。GitHubにこのプロジェクトのソースコードを残しておきます。
1 deviseの導入
1.1 プロジェクトの作成
新しいプロジェクトを作ります。
$ rails new devise_omniauth
$ cd devise_omniauth
1.2 Gemfileの追加とインストール
Gemfileに以下のgemを追加する。
source 'https://rubygems.org'
(省略)...
# Devise
gem 'devise'
gem 'devise-i18n'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'dotenv-rails'
gem 'devise' #ユーザー認証
gem 'devise-i18n' #deviseのi18n
gem 'omniauth-twitter' #twitter認証
gem 'omniauth-facebook' #facebook認証
gem 'dotenv-rails' #環境変数の設定
gemをインストール。
$ bundle install
2 deviseの設定
devise関連ファイルを追加。
$ rails g devise:install
このコマンドを実行すると、ターミナルに英文でdeviseの設定について記載されています。
それでは1〜4まで実行していきましょう。
2.1 デフォルトURLの指定
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
(省略)...
# mailer setting
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end
2.2 root_urlの指定
1番で指定した*http://localhost:3000/*にアクセスした際に表示されるページを指定します。
このプロジェクトではページを1つも作っていないため、先に追加します。
Pagesコントローラーと、indexページとshowページを追加してみます。
$ rails g controller Pages index show
routes.rbに以下を指定します。
Rails.application.routes.draw do
root 'pages#index'
get 'pages/show'
(省略)...
end
2.3 フラッシュメッセージの追加
ログインした時などに上の方に「ログインしました」みたいなメッセージが出るようにします。
以下のファイルの<body>
タグのすぐ下に指定されたタグを挿入します。
<!DOCTYPE html>
<html>
<head>
<title>DeviseRails5</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%# ここから %>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%# ここまで %>
<%= yield %>
</body>
</html>
2.4 DeviseのViewを生成
$ rails g devise:views
すると以下の様なファイルが生成されます。
app/views/devise/shared/_links.html.erb (リンク用パーシャル)
app/views/devise/confirmations/new.html.erb (認証メールの再送信画面)
app/views/devise/passwords/edit.html.erb (パスワード変更画面)
app/views/devise/passwords/new.html.erb (パスワードを忘れた際、メールを送る画面)
app/views/devise/registrations/edit.html.erb (ユーザー情報変更画面)
app/views/devise/registrations/new.html.erb (ユーザー登録画面)
app/views/devise/sessions/new.html.erb (ログイン画面)
app/views/devise/unlocks/new.html.erb (ロック解除メール再送信画面)
app/views/devise/mailer/confirmation_instructions.html.erb (メール用アカウント認証文)
app/views/devise/mailer/password_change.html.erb (メール用パスワード変更完了文)
app/views/devise/mailer/reset_password_instructions.html.erb (メール用パスワードリセット文)
app/views/devise/mailer/unlock_instructions.html.erb (メール用ロック解除文)
3 Userモデルの設定
3.1 Userモデルの作成
$ rails g devise User
を実行するとmigrationファイルとuserファイルが出来上がります。
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
# ## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
# ## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
# ## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
end
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
3.2 マイグレーションファイル、Userモデルの編集
これを使うものだけコメントアウトしていきます。
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token, unique: true
add_index :users, :unlock_token, unique: true
end
end
マイグレーションファイルで入れるものに加え、OAuth認証をするのでomniauth_providers: [:twitter,:facebook]
を追加します。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable, omniauth_providers: [:twitter,:facebook]
end
3.3 omniauth用カラムの追加
ついでにomniauth-twitter,omniauth-facebookで使うprovider
、uid
、username
をUserテーブルに追加します。
$ rails g migration add_columns_to_users provider uid username
以下のようなマイグレーションファイルができます。
class AddColumnsToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
add_column :users, :username, :string
end
end
ここまで出来たら以下を実行します。
$ rake db:migrate
4 Twitter,facebookで認証する
4.1 Twitter認証,Facebook認証をするためのそれぞれのAPIキー、シークレットキーを取得する
以下よりアプリケーションを作成する。
作成が完了したら、設定より「Add Platform」→「Website」を選択する。
サイトURLにURLを入力する(例:http://localhost:3000
)。
以下よりアプリケーションを作成する。
作成が完了したら、「Settings」より以下の設定を行なう。
- Callback URL
- 例:
http://〜/users/auth/twitter
- 以下にチェックを入れる:
- Allow this application to be used to Sign in with Twitter
4.2 設定ファイルの編集
それぞれのAPIキー、シークレットキーを以下の該当箇所にコピーして貼り付けます。
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
(省略)...
config.omniauth :facebook, 'App IDを入力', 'App Secretを入力' #すぐに訂正します
config.omniauth :twitter, 'API keyを入力', 'API secretを入力' #すぐに訂正するのでGitHubにコミットしないでください
end
4.3 Userコントローラにコールバック処理を実装
provider
と同じ名前のメソッドを定義する必要がある。
ただ、基本的に各プロバイダでのコールバック処理は共通しているので、callback_from
メソッドに統一している。
$ rails generate devise:controllers users
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
callback_from :facebook
end
def twitter
callback_from :twitter
end
private
def callback_from(provider)
provider = provider.to_s
@user = User.find_for_oauth(request.env['omniauth.auth'])
if @user.persisted?
flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
sign_in_and_redirect @user, event: :authentication
else
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
end
4.4 ルーティング処理
以下のように、OAuthのコールバック用のルーティングを設定する。
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
# ...
end
5 APIキー,シークレットキーの非公開
Twitter認証,Facebook認証をするためのそれぞれのAPIキー、シークレットキーを取得しました、この情報は非常に機密性が高く決して外部に漏らしてはいけない情報です。悪用される恐れがあるため、GitHubのリモートリポジトリや本番環境でAPIキー,シークレットキーが間違って一般公開されないような処理を施す必要があります。
最初の方で環境変数を設定するためのgem 'dotenv-rails'
をbundle install
しているので、dotenv-rails
を使ったAPIキー、シークレットキーの方法を学んでいきます。
5.1 .envファイルの設置
次に、環境変数を定義する.envファイルをアプリのプロジェクトルート直下に設置します。
.envファイルはdotenv-rails
をbundle install
しても自動生成されない為、下記の様にtouchコマンドを使って手動でファイルを作成する必要があります。
$ touch .env
5.2 .envファイルに以下を記載する
TWITTER_API_KEY="取得したTwitterAPIキー"
TWITTER_SECRET_KEY="取得したTwitterシークレットキー"
FACEBOOK_API_ID="取得したFacebookAPIキー"
FACEBOOK_API_SECRET="取得したFacebookシークレットキー"
5.3 環境変数を使う
ENV['SECRET_KEY']
のような記載方法で、ハードコーディングしているファイルに環境変数を代入していきます。
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
(省略)...
config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET_KEY']
config.omniauth :facebook, ENV['FACEBOOK_API_ID'], ENV['FACEBOOK_API_SECRET']
end
6 ユーザーモデルにメソッドを追加する
Userモデルにself.from_omniauthとself.new_with_sessionを作ります。
self.from_omniauthではuidとproviderで検索してあったらそれを、無かったらレコードを作ります。
self.new_with_sessionについては、もしこのメソッドを追加しておかなければ、Twitter認証後サインアップページで登録を行っても、認証情報として取ってきたuidやproviderなどが登録されません。それらが登録されないのでTwitterで認証しても登録されてないユーザーとして毎回サインアップページに飛ばされます。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable, omniauth_providers: [:twitter]
def self.from_omniauth(auth)
find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.username = auth["info"]["nickname"]
end
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"]) do |user|
user.attributes = params
end
else
super
end
end
end
6.1 Userモデルにfindメソッドを実装
uid
とprovider
の組み合わせは一意であり、これによりユーザを取得する。
レコードに存在しない場合は作成する。
class User < ActiveRecord::Base
# ...
def self.find_for_oauth(auth)
user = User.where(uid: auth.uid, provider: auth.provider).first
unless user
user = User.create(
uid: auth.uid,
provider: auth.provider,
email: User.dummy_email(auth),
password: Devise.friendly_token[0, 20]
)
end
user.skip_confirmation! #仮登録メールを介さずに即時登録
user
end
private
def self.dummy_email(auth)
"#{auth.uid}-#{auth.provider}@example.com"
end
end
メールアドレスでの認証も実装している場合、OAuthでの認証時もメールアドレスを保存する必要がある。
ここでは、uid
とprovider
の組み合わせが一意なことを利用して、self.dummy_email
のように生成している。
以下ファイルを編集して、コールバック用のコントローラーとしてさっき作ったコントローラーが呼ばれるようにします。これを書かないとdevise側のコントローラーが呼ばれます。
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
root 'pages#index'
get 'pages/show'
(省略)...
end
これでTwitter認証ができるようになりました。
初回、Twitter認証を行うと、サインアップページに飛ばされ、そこでメールアドレスやパスワードを入力して登録するとユーザー情報が登録されます。
今回はcomfirmable機能を入れているので、登録したら確認メッセージを送ったとのメッセージが出て、そのままログインすることはできません。
この機能を入れてなかった場合、登録すると即ログインします。
7 SNS認証には仮登録メールを介さずに即時登録となるようにしたい
class User < ActiveRecord::Base
# ...
def self.find_for_oauth(auth)
user = User.where(uid: auth.uid, provider: auth.provider).first
unless user
user = User.create(
uid: auth.uid,
provider: auth.provider,
email: User.dummy_email(auth),
password: Devise.friendly_token[0, 20]
)
end
######これを追記!######
user.skip_confirmation!
#######################
user
end
private
def self.dummy_email(auth)
"#{auth.uid}-#{auth.provider}@example.com"
end
end
8 ユーザー情報の編集で逐一パスワードを求められるのがだるい
8.1 routes.rbを修正する
devise_for :users, controllers: { }
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations' }
root 'pages#index'
get 'pages/show'
(省略)...
end
8.2 update_resourceメソッドをオーバーライドする
class RegistrationsController < Devise::RegistrationsController
protected
# 追記する
def update_resource(resource, params)
resource.update_without_password(params)
end
end
8.3 current_passwordフォームを削除する
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
こちらのフォームを削除しましょう。
これで、パスワードを入力しなくてもユーザーの登録情報を
編集することが可能になりました!
9 やりたいことの文献一覧
基本的なユーザー認証機能に加え、SNS認証でワンクリック登録できる仕組みの構築。
[Rails] deviseの使い方(rails5版)
RailsにDevise+OmniAuthでユーザ認証を実装する手順
環境変数を使用し、APIキーを隠してリモートにpushしたい(App Secretなどをハードコーディングしているのはよろしくない)
【Rails】dotenv-railsの導入方法と使い方を理解して環境変数を管理しよう!
環境変数の設定
ダミーではなく、twitterやfacebookに登録されたemailをDBを持っていきたい(3日くらいかかる)
omniauth-twitterでemail情報を取得する
twitterのoauthを使ってみる(emailも取得)
facebookのoauthを使ってみる(emailも取得)
2015年7月9日以降にFacebook認証でメールアドレスが取れない問題とその対策
TwitterやGoogle,Githubなどの外部サイトを用いた認証には仮登録メールを介さずに即時登録となるようにしたい
Devise内でomniauthのtwitter認証が完了してもレコードが格納されない
deviseのTwitterログイン時はメール認証とメール送信をスキップする
【Rails5】SNS認証でメールアドレス介さず登録・ログインできるようにする実装
deviseをi18nで日本語にしたい
i18nで日本語化
ユーザー情報の編集で逐一パスワードを求められるのがだるい
[Devise] パスワードを入力せずにユーザー情報を編集する
メールアドレスのみでユーザー登録を行う。
devise でメールアドレスのみでユーザー登録を行い、パスワードを後から設定する方法
How To: Email only sign up
サインインする際にメールアドレス以外でサインインする方法
How To: Allow users to sign in with something other than their email address
メールアドレスのアップデートをする際に確認を必要としない方法(スキップしたい)
deviseでメールアドレスのアップデートを確認する必要はありませんか?
Deviseでメールアドレスの確認をスキップする
その他
【Rails】deviseのTwitter認証で「Unauthorized 403 Forbidden」が出てしまう場合の対処法
deviseのドキュメント(英語)