参照
- Railsでユーザー認証機能を実装しよう~定番のgem「devise」活用法(2)
- RailsでSNS認証を実装しよう〜定番gem「OmniAuth」活用法
- Devise with Hyperloop Tutorial
- OmniAuth Azure Active Directory
model名を使うところと、model名に関係なく user とするところがあるので、区別するために model名を Userではなく Agentとして試してみました。
devise gem の導入
#### devise gemのインストールgem 'devise'
$ bundle install
$ bundle exec rails generate devise:install
modelの生成 と migrate
$ bundle exec rails generate devise Agent
Running via Spring preloader in process 468
invoke active_record
create db/migrate/20190622011638_add_devise_to_agents.rb
insert app/models/agent.rb
route devise_for :agents
$ bundle exec rails db:migrate
== 20190630011828 DeviseCreateAgents: migrating ==
-- create_table(:agents)
-> 0.0010s
-- add_index(:agents, :reset_password_token, {:unique=>true})
-> 0.0007s
== 2019063011828 DeviseCreateAgents: migrated (0.0030s) ===
app/models/agent.rb を app/hyperstack/models/agent.rb へ移動
ここまでで、http://localhost:5000/agents/sign_up にサインアップ画面が見られるようになる。
application_controller.rb に、before_action :authenticate_<model名>! を追加
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_agent! # 追加
これで http://localhost:5000 にアクセスすると、まず Log in画面が表示されるようになる。
サインアップしてログインすれば、もとのHyperstackアプリに戻ることができる。
current_<model名> の aliasとして acting_user を作る
acting_<model名> ではなく acting_user にする
def acting_user
current_agent
end
これで、Hyperstack::Application.acting_user_id で、ログイン中のAgentのidにアクセスできるようになる。
model にクラスメソッド current を定義
class << self
def current
id = Hyperstack::Application.acting_user_id
id ? find(id) : new
end
model の devise行に unless RUBY_ENGINE == 'opal' を追加
class Agent < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable unless RUBY_ENGINE == 'opal'
omniAuth AzureADの導入
#### 先に Azure Portalにアプリを登録してCLIENT IDとTENANT IDを取得しておく [Azureポータル](https://portal.azure.com/#home)Azureポータルには、「アプリケーション(クライアントID)」、「ディレクトリ(テナントID)」と書かれている
"ID Token"をenableにしておく
そうしないと、あとでこのエラーを見ることになる。参考 stackoverflow Q&A
AADSTS700054: response_type 'id_token' is not enabled for the application.
"App registrations"で登録したアプリ名を開いて、左側の「認証」を選択し、「ID トークン」をチェック
"Redirect URI"に、callbackを設定
http://localhost:5000/agents/auth/azureactivedirectory/callback
omniauth gem と dotenv-rails のインストール
group :development, :test do
...
gem 'dotenv-rails'
end
...
gem 'omniauth'
gem 'omniauth-azure-activedirectory'
$ bundle install
.env に、Azureポータルから取得した CLIENT ID と TENANT IDを設定しておく
AAD_CLIENT_ID = 'abcdabcd-abcd-abcd-abcd-abcdabcdabcd'
AAD_TENANT = 'abcdabcd-abcd-abcd-abcd-abcdabcdabcd'
(githubなどにアップするなら .envを .gitignoreに登録しておくのを忘れずに)
config/initializers/devise.rb に 登録
config.omniauth :azure_activedirectory, ENV['AAD_CLIENT_ID'], ENV['AAD_TENANT']
modelに provider, uid カラムを追加
必須ではないが、後で使うために name カラムも追加しておく
$ bundle exec rails generate migration AddColumnsToAgents name:string provider:string uid:string
$ bundle exec rails db:migrate
Deviseにcontrollerを作らせる
$ bundle exec rails g devise:controllers agents
Running via Spring preloader in process 341
create app/controllers/agents/confirmations_controller.rb
create app/controllers/agents/passwords_controller.rb
create app/controllers/agents/registrations_controller.rb
create app/controllers/agents/sessions_controller.rb
create app/controllers/agents/unlocks_controller.rb
create app/controllers/agents/omniauth_callbacks_controller.rb
=========================================================================
Some setup you must do manually if you haven't yet:
Ensure you have overridden routes for generated controllers in your routes.rb.
For example:
Rails.applicationo.routes.draw do
devise_for :users, controllers: {
sessions: 'users/sessions'
}
end
===========================================
routingを定義
Rails.application.routes.draw do
devise_for :agents, controllers: {
omniauth_callbacks: 'agents/omniauth_callbacks',
registrations: 'agents/registrations'
}
mount Hyperstatck::Engine => '/hyperstack'
get '/(*other)', to: 'hyperstack#app'
end
Modelの devise行に :omniauthable 等を追加
class Agent < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable, omniauth_providers: [:azureactivedirectory] unless RUBY_ENGINE == 'opal'
:azure_activedirectory ではなく、:azureactivedirectory にする。
でないと、サインインしようとしたときに "Not found. Authentication passthru" を見ることになる。
Devise issue# 4208
callbackを定義し、protect_from_forgery から :azureactivedirectory を除外する
class Agents::OmniauthCallbacksController < Devise::OmniauthCallbacksController
protect_from_forgery with: :exception, except: [:azureactivedirectory] # 追加
def azureactivedirectory
callback
end
def passthru
super
end
def failure
super
end
private
def callback
@agent = Agent.find_or_create_for_oauth(request.env['omniauth.auth'])
if @agent.persisted?
sign_in_and_redirect @agent
else
session['devise.user_attributes'] = @agent.attributes
redirect_to new_agent_registration_url
end
end
end
class Agent < ApplicationRecord
…(略)…
class << self
def current
id = Hyperstack::Application.acting_user_id
id ? find(id) : new
end
def find_or_create_for_oauth(auth)
find_or_create_by!(email: auth.info.email.downcase) do |user| # downcaseが必要
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
password = Devise.friendly_token[0..5]
logger.debug password
user.password = password
end
end
def new_with_session(params, session)
if user_attributes = session['devise.user_attributes']
new(user_attributes) { |user| user.attributes = params }
else
super
end
end
end
end
これで、http://localhost:5000 で表示される "Sign in with Azureactivedirectory" から AzureADの認証へ進むようになった。
Login画面を通さず、リンクのクリックでAzureAD認証をさせる
#### HeaderコンポーネントのNavbarにLoginリンクを置いてみる ログイン後はリンクの代わりにAgent.current.nameを表示させるclass Header < HyperComponent
render(HEADER) do
Navbar('light', color: 'success') do
Nav(class: 'ml-auto') do
NavItem do
Agent.current.name || login_link
end
end
end
end
def login_link
NavLink(class: 'login-link', href: 'agents/auth/azureactivedirectory') do
'ログイン'
end
end
end
リンク先に agent_azureactivedirectory_omniauth_authorize_path を使いたいが、Hyperstackコンポーネントから使う方法がわからないので、パス 'agents/auth/azureactivedirectory' を直接指定した。
Login画面を表示しないようにする
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# before_action :authenticate_agent! # コメントアウト
def acting_user
current_agent
end
end
これで、「ログイン」リンクをクリックすると、AzureADの認証に進むようになった。